Android G.711音频编解码

    xiaoxiao2021-03-25  364

    需求背景:

    博主目前所在的公司是一家做视频通讯的公司,所以对音频,视频这一块对编码方式都有一定的要求,由于之前一直没有接触JNI这一块,突然让我去做音频的转码还是有一定的苦难的。一开始对于JNI编程我是拒绝的,一直遵循着能用java源码,就绝不用Jni那一块。但是,显示总是残酷的,网上的资料,Demo很少,或者都是年代久远,还不能运行的。所以我抱着试一试的心态去接触JNI,也还蛮有收获的,好了废话了这么多,也该进入主题了。

    Demo主要功能:

    AndroidStudio项目,在安卓平台下,调用G.711 C++编解码方法进行音频源的编码和解码。Demo在文章末尾。

    G.711 C++源码

    http://download.csdn.net/download/orient1860/7691041 待会我demo里面也有

    Android 源码

    package com.chezi008.hellojni.encode; import android.annotation.SuppressLint; import android.app.Activity; import android.graphics.PixelFormat; import android.media.AudioFormat; import android.media.AudioRecord; import android.media.MediaRecorder; import android.os.Bundle; import android.view.View; import android.view.View.OnClickListener; import android.view.Window; import android.view.WindowManager; import android.widget.Button; import com.chezi008.hellojni.R; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; /** * 描述:G.711 编码与解码 * 作者:chezi008 on 2017/3/10 16:33 * 邮箱:chezi008@163.com */ @SuppressLint("SdCardPath") public class G711DecoderActivity extends Activity { // 音频获取源 private int audioSource = MediaRecorder.AudioSource.MIC; // 设置音频采样率,44100是目前的标准,但是某些设备仍然支持22050,16000,11025 private static int sampleRateInHz = 8000; // 设置音频的录制的声道CHANNEL_I N_STEREO为双声道,CHANNEL_CONFIGURATION_MONO为单声道 private static int channelConfig = AudioFormat.CHANNEL_IN_STEREO; // 音频数据格式:PCM 16位每个样本。保证设备支持。PCM 8位每个样本。不一定能得到设备支持。 private static int audioFormat = AudioFormat.ENCODING_PCM_16BIT; // 缓冲区字节大小 private int bufferSizeInBytes = 0; private Button Start; private Button Stop; private Button convert; private AudioRecord audioRecord; private boolean isRecord = false;// 设置正在录制的状态 //AudioName裸音频数据文件,编码后的文件 private static final String AudioName = "/sdcard/end.g711"; //解码后的文件 private static final String AudioDecodeName = "/sdcard/endDecode.g711"; //NewAudioName可播放的音频文件 private static final String NewAudioName = "/sdcard/new.wav"; private G711Decoder codec; public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); getWindow().setFormat(PixelFormat.TRANSLUCENT);// 让界面横屏 requestWindowFeature(Window.FEATURE_NO_TITLE);// 去掉界面标题 getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN); // 重新设置界面大小 setContentView(R.layout.activity_g711); init(); } private void init() { Start = (Button) this.findViewById(R.id.start); Stop = (Button) this.findViewById(R.id.stop); convert = (Button) findViewById(R.id.convert); Start.setOnClickListener(new TestAudioListener()); Stop.setOnClickListener(new TestAudioListener()); convert.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { // new PlayTask().execute(); new Thread(new AudioConvert()).start(); } }); codec = new G711Decoder(); creatAudioRecord(); } private void creatAudioRecord() { // 获得缓冲区字节大小 bufferSizeInBytes = AudioRecord.getMinBufferSize(sampleRateInHz, channelConfig, audioFormat); // 创建AudioRecord对象 audioRecord = new AudioRecord(audioSource, sampleRateInHz, channelConfig, audioFormat, bufferSizeInBytes); } class TestAudioListener implements OnClickListener { @Override public void onClick(View v) { if (v == Start) { startRecord(); } if (v == Stop) { stopRecord(); } } } private void startRecord() { audioRecord.startRecording(); // 让录制状态为true isRecord = true; // 开启音频文件写入线程 new Thread(new AudioRecordThread()).start(); } private void stopRecord() { close(); } private void close() { if (audioRecord != null) { System.out.println("stopRecord"); isRecord = false;//停止文件写入 audioRecord.stop(); audioRecord.release();//释放资源 audioRecord = null; } } class AudioRecordThread implements Runnable { @Override public void run() { writeDateTOFile();//往文件中写入裸数据 // copyWaveFile(AudioName, NewAudioName);//给裸数据加上头文件 } } class AudioConvert implements Runnable { @Override public void run() { decodeAudio(AudioName, AudioDecodeName); copyWaveFile(AudioDecodeName, NewAudioName);//给裸数据加上头文件 } } /** * 解码音频文件 * @param inFilename * @param outFilename */ private void decodeAudio(String inFilename, String outFilename) { FileInputStream in = null; FileOutputStream out = null; byte[] data = new byte[1024]; byte[] outData = new byte[2048];//这里特别注意outData是data的两倍,之前不知道,一直卡在这里,是的解码进行不下去 try { in = new FileInputStream(inFilename); out = new FileOutputStream(outFilename); int length = 0; while ((length = in.read(data)) != -1) { codec.VoiceDecode(data, outData, length); out.write(outData); } in.close(); out.close(); } catch (FileNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } } /** * 这里将数据写入文件,但是并不能播放,因为AudioRecord获得的音频是原始的裸音频, * 如果需要播放就必须加入一些格式或者编码的头信息。但是这样的好处就是你可以对音频的 裸数据进行处理,比如你要做一个爱说话的TOM * 猫在这里就进行音频的处理,然后重新封装 所以说这样得到的音频比较容易做一些音频的处理。 */ private void writeDateTOFile() { // new一个byte数组用来存一些字节数据,大小为缓冲区大小 byte[] audiodata = new byte[bufferSizeInBytes]; FileOutputStream fos = null; int readsize = 0; try { File file = new File(AudioName); if (file.exists()) { file.delete(); } fos = new FileOutputStream(file);// 建立一个可存取字节的文件 } catch (Exception e) { e.printStackTrace(); } while (isRecord == true) { readsize = audioRecord.read(audiodata, 0, bufferSizeInBytes); byte[] alawData = new byte[readsize]; if (AudioRecord.ERROR_INVALID_OPERATION != readsize) { try { int len = codec.VoiceEncode(audiodata, alawData, readsize); //调用C代码进行编码 fos.write(alawData, 0, len); //保存到本地 } catch (IOException e) { e.printStackTrace(); } } } try { fos.close();// 关闭写入流 } catch (IOException e) { e.printStackTrace(); } } /** * 解码后增加音频文件的头部,是的文件可播放 * @param inFilename * @param outFilename */ private void copyWaveFile(String inFilename, String outFilename) { FileInputStream in = null; FileOutputStream out = null; long totalAudioLen = 0; long totalDataLen = totalAudioLen + 36; long longSampleRate = sampleRateInHz; int channels = 2; long byteRate = 16 * sampleRateInHz * channels / 8; byte[] data = new byte[bufferSizeInBytes]; try { in = new FileInputStream(inFilename); out = new FileOutputStream(outFilename); totalAudioLen = in.getChannel().size(); totalDataLen = totalAudioLen + 36; WriteWaveFileHeader(out, totalAudioLen, totalDataLen, longSampleRate, channels, byteRate); while (in.read(data) != -1) { out.write(data); } in.close(); out.close(); } catch (FileNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } } /** * 这里提供一个头信息。插入这些信息就可以得到可以播放的文件。 * 为我为啥插入这44个字节,这个还真没深入研究,不过你随便打开一个wav * 音频的文件,可以发现前面的头文件可以说基本一样哦。每种格式的文件都有 * 自己特有的头文件。 */ private void WriteWaveFileHeader(FileOutputStream out, long totalAudioLen, long totalDataLen, long longSampleRate, int channels, long byteRate) throws IOException { byte[] header = new byte[44]; header[0] = 'R'; // RIFF/WAVE header header[1] = 'I'; header[2] = 'F'; header[3] = 'F'; header[4] = (byte) (totalDataLen & 0xff); header[5] = (byte) ((totalDataLen >> 8) & 0xff); header[6] = (byte) ((totalDataLen >> 16) & 0xff); header[7] = (byte) ((totalDataLen >> 24) & 0xff); header[8] = 'W'; header[9] = 'A'; header[10] = 'V'; header[11] = 'E'; header[12] = 'f'; // 'fmt ' chunk header[13] = 'm'; header[14] = 't'; header[15] = ' '; header[16] = 16; // 4 bytes: size of 'fmt ' chunk header[17] = 0; header[18] = 0; header[19] = 0; header[20] = 1; // format = 1 header[21] = 0; header[22] = (byte) channels; header[23] = 0; header[24] = (byte) (longSampleRate & 0xff); header[25] = (byte) ((longSampleRate >> 8) & 0xff); header[26] = (byte) ((longSampleRate >> 16) & 0xff); header[27] = (byte) ((longSampleRate >> 24) & 0xff); header[28] = (byte) (byteRate & 0xff); header[29] = (byte) ((byteRate >> 8) & 0xff); header[30] = (byte) ((byteRate >> 16) & 0xff); header[31] = (byte) ((byteRate >> 24) & 0xff); header[32] = (byte) (2 * 16 / 8); // block align header[33] = 0; header[34] = 16; // bits per sample header[35] = 0; header[36] = 'd'; header[37] = 'a'; header[38] = 't'; header[39] = 'a'; header[40] = (byte) (totalAudioLen & 0xff); header[41] = (byte) ((totalAudioLen >> 8) & 0xff); header[42] = (byte) ((totalAudioLen >> 16) & 0xff); header[43] = (byte) ((totalAudioLen >> 24) & 0xff); out.write(header, 0, 44); } @Override protected void onDestroy() { close(); super.onDestroy(); } }

    G711Decoder类我就不贴了,可以去Demo里面找。

    Demo地址

    http://download.csdn.net/detail/chezi008/9777122

    转载请注明原文地址: https://ju.6miu.com/read-23015.html

    最新回复(0)