FFmpeg 音频编码(PCM数据编码成AAC android)
之前做的直播设计到音视频编码、rtmp推流、rtmp流播放等内容,现在抽时间整理一下。首先说一下音频编码(pcm 编码得到aac数据)。
1 录音(获取pcm数据)
开始录音
private void startRecord(){Log.i(TAG, "startRecord mIsRecording="+mIsRecording);if(!mIsRecording){mIsRecording = true;synchronized (mLock) {mAudioRecordGetExit = false;}//初始化ffmpeg 编码器mFFAacEncoderJni.start();//创建录音线程、开始录音mAudioRecordGetThread = new Thread(new AudioRecordGet());mAudioRecordGetThread.start();}}
关闭录音
private void stoptRecord(){if(mIsRecording){synchronized (mLock) {mAudioRecordGetExit = true;}mIsRecording = false;}}
private class AudioRecordGet implements Runnable{private AudioRecord mAudioRecord;private static final boolean PCM_DUMP_DEBUG = true;private static final boolean AAC_DUMP_DEBUG = false;private int mAudioSource = MediaRecorder.AudioSource.MIC;//采样频率,采样频率越高,音质越好。44100 、22050、 8000、4000等private int mSampleRateHz = 8000;//MONO为单声道 ,STEREO为双声道private int mChannelConfig = AudioFormat.CHANNEL_IN_MONO;//编码格式和采样大小,pcm编码;支持的采样大小16bit和8bit,采样大小越大,信息越多,音质越好。private int mAudioFormat = AudioFormat.ENCODING_PCM_16BIT;//该size设置为AudioRecord.getMinBufferSize(mSampleRateHz, mChannelConfig, mAudioFormat); 编码aac时会失败。private int mBufferSizeInBytes = 2048;//AudioRecord.getMinBufferSize(mSampleRateHz, mChannelConfig, mAudioFormat);private AudioPCMData mAudioPCMData;public AudioRecordGet() {Log.i(TAG, "AudioRecordGet ");mAudioPCMData = new AudioPCMData(mBufferSizeInBytes);mAudioRecord = new AudioRecord(mAudioSource,mSampleRateHz, mChannelConfig, mAudioFormat, mBufferSizeInBytes);Log.i(TAG,"mBufferSizeInBytes="+mBufferSizeInBytes);}@Overridepublic void run() {mAudioRecord.startRecording();FileOutputStream outPCM = null;try {if (PCM_DUMP_DEBUG) {String File = "/sdcard/test.pcm";outPCM = new FileOutputStream(File);}} catch (Exception e) {e.printStackTrace();}for(;;){synchronized (mLock) {if(mAudioRecordGetExit){break;}}//读取录音数据int readSize = mAudioRecord.read(mAudioPCMData.mData, 0, mBufferSizeInBytes);if (AudioRecord.ERROR_INVALID_OPERATION != readSize) {if (PCM_DUMP_DEBUG && null != outPCM) {try {outPCM.write(mAudioPCMData.mData, 0, readSize);} catch (Exception e) {e.printStackTrace();}}mAudioPCMData.mFrameSize = readSize;Log.i(TAG, "audio pcm size="+readSize);//设置pcm数据,进行aac编码mFFAacEncoderJni.setPcmData(mAudioPCMData.mData, readSize);}}if(PCM_DUMP_DEBUG && null != outPCM){try {outPCM.close();} catch (IOException e) {e.printStackTrace();}}//停止录音、释放mAudioRecord.stop();mAudioRecord.release();//停止音频编码mFFAacEncoderJni.stop();Log.i(TAG,"AudioRecordGet thread exit success");}}
2 PCM编码成AAC
1 初始化编码器
//初始化ffmpeg 编码器mFFAacEncoderJni.start();
FFAacEncoder代码如下
public class FFAacEncoder {private String TAG = "FFAacEncoder java";//load .sostatic{System.loadLibrary("avcodec-57");System.loadLibrary("avdevice-57");System.loadLibrary("avfilter-6");System.loadLibrary("avformat-57");System.loadLibrary("avutil-55");System.loadLibrary("postproc-54");System.loadLibrary("swresample-2");System.loadLibrary("swscale-4");System.loadLibrary("aacEncoder");}private int mNativeContext = 0;//初始化编码器private native final void nativeStart();//对pcm数据进行编码private native final void nativeSetPcmData(byte[] pcm, int len);//必要的清理private native final void nativeStop();public void start(){nativeStart();}public void setPcmData(byte[] pcm, int len){nativeSetPcmData(pcm, len);}public void stop(){nativeStop();}}
调用nativeStart方法。 最终调的代码如下(初始化):
int AacCodec::start(){ALOGW("start");av_register_all();//avcodec_register_all();mAVCodec = avcodec_find_encoder(AV_CODEC_ID_AAC);//查找AAC编码器if(!mAVCodec){ALOGE("encoder AV_CODEC_ID_AAC not found");return -1;}mAVCodecContext = avcodec_alloc_context3(mAVCodec);if(mAVCodecContext != NULL){mAVCodecContext->codec_id = AV_CODEC_ID_AAC;mAVCodecContext->codec_type = AVMEDIA_TYPE_AUDIO;mAVCodecContext->bit_rate = 12200;mAVCodecContext->sample_fmt = AV_SAMPLE_FMT_FLTP;mAVCodecContext->sample_rate = 8000;mAVCodecContext->channel_layout = AV_CH_LAYOUT_MONO;mAVCodecContext->channels = av_get_channel_layout_nb_channels(mAVCodecContext->channel_layout);}else {ALOGE("avcodec_alloc_context3 fail");return -1;}ALOGW("start 3 channels %d",mAVCodecContext->channels);if(avcodec_open2(mAVCodecContext, mAVCodec, NULL) < 0){ALOGE("aac avcodec open fail");av_free(mAVCodecContext);mAVCodecContext = NULL;return -1;}mAVFrame = av_frame_alloc();if(!mAVFrame) {avcodec_close(mAVCodecContext);av_free(mAVCodecContext);mAVCodecContext = NULL;return -1;}mAVFrame->nb_samples = mAVCodecContext->frame_size;mAVFrame->format = mAVCodecContext->sample_fmt;mAVFrame->channel_layout = mAVCodecContext->channel_layout;mBufferSize = av_samples_get_buffer_size(NULL, mAVCodecContext->channels, mAVCodecContext->frame_size, mAVCodecContext->sample_fmt, 0);if(mBufferSize < 0){ALOGE("av_samples_get_buffer_size fail");av_frame_free(&mAVFrame);mAVFrame = NULL;avcodec_close(mAVCodecContext);av_free(mAVCodecContext);mAVCodecContext = NULL;return -1;}mEncoderData = (uint8_t *)av_malloc(mBufferSize);if(!mEncoderData){ALOGE("av_malloc fail");av_frame_free(&mAVFrame);mAVFrame = NULL;avcodec_close(mAVCodecContext);av_free(mAVCodecContext);mAVCodecContext = NULL;return -1;}avcodec_fill_audio_frame(mAVFrame, mAVCodecContext->channels, mAVCodecContext->sample_fmt, (const uint8_t*)mEncoderData, mBufferSize, 0);if(DUMP_DEBUG){//存AAC原始音频数据mAVFormatContext = avformat_alloc_context();mAVOUtputFormat = av_guess_format(NULL, outFile, NULL);mAVFormatContext->oformat = mAVOUtputFormat;//Open output URLif (avio_open(&mAVFormatContext->pb, outFile, AVIO_FLAG_READ_WRITE) < 0){printf("Failed to open output file!\n");return -1;}mAVStream = avformat_new_stream(mAVFormatContext, 0);if (!mAVStream){return -1;}av_dump_format(mAVFormatContext, 0, outFile, 1);//Write Headeravformat_write_header(mAVFormatContext, NULL);}return 0;}
2 音频编码
//设置pcm数据,进行aac编码mFFAacEncoderJni.setPcmData(mAudioPCMData.mData, readSize);
调用nativeSetPcmData C++层代码,通过编码获取的AAC原始数据不同播放(存储在/sdcard/test.aac文件中,不能播放),
需要添加adts header(不懂的可以了解一下AAC格式),这样才可以正常播放。/sdcard/adts.aac该文件添加了header,可以正常播放。
int AacCodec::encode_pcm_data(void* pIn, int frameSize){int encode_ret = -1;int got_packet_ptr = 0;AVPacket pkt;av_init_packet(&pkt);pkt.data = NULL;pkt.size = 0;if(mAVCodecContext && mAVFrame){short2float((int16_t *)pIn, mEncoderData, frameSize/2);mAVFrame->data[0] = mEncoderData;mAVFrame->pts = 0;//音频编码encode_ret = avcodec_encode_audio2(mAVCodecContext, &pkt, mAVFrame, &got_packet_ptr);ALOGW("encode_pcm_data ----encode_ret:%d,got_packet_ptr:%d",encode_ret,got_packet_ptr);if(encode_ret < 0){ALOGE("Failed to encode!\n");return encode_ret;}ALOGW("encode_pcm_data ----size =%d",pkt.size);pkt.stream_index = mAVStream->index;void *adts;if(DUMP_DEBUG && pkt.size > 0){if(mADTSFile){void *adts = malloc(ADTS_HEADER_LENGTH); //添加adts header 可以正常播放。 addADTSheader((uint8_t *)adts, pkt.size+ADTS_HEADER_LENGTH);fwrite(adts, 1, ADTS_HEADER_LENGTH, mADTSFile);fwrite(pkt.data, 1, pkt.size, mADTSFile);free(adts);}//无adts header,aac原始数据,不能播放mAVOUtputFormat->write_packet(mAVFormatContext, &pkt);}av_free_packet(&pkt);}return encode_ret;}
添加adts header 代码。这里要设置采样率下标(不同采样率对应不同下标,这个可以查一下),声道数。
void AacCodec::addADTSheader(uint8_t * in, int packet_size){int sampling_frequency_index = 11; //采样率下标,11表示8000int channel_configuration = mAVCodecContext->channels; //声道数in[0] = 0xFF;in[1] = 0xF9;in[2] = 0x40 | (sampling_frequency_index << 2) | (channel_configuration >> 2);//0x6c;in[3] = (channel_configuration & 0x3) << 6;in[3] |= (packet_size & 0x1800) >> 11;in[4] = (packet_size & 0x1FF8) >> 3;in[5] = ((((unsigned char)packet_size) & 0x07) << 5) | (0xff >> 3);in[6] = 0xFC;}
3 注意
我这里录音
采样率为8000HZ、声道为单声道,mBufferSizeInBytes 使用AudioRecord.getMinBufferSize(mSampleRateHz, mChannelConfig, mAudioFormat); 编码时会失败,使用2048 和4096都没有问题。
如果采样率要用16000HZ、声道为双声道,则要修改的地方有
private int mSampleRateHz = 16000;//MONO为单声道 ,STEREO为双声道private int mChannelConfig = AudioFormat.CHANNEL_IN_STEREO;private int mBufferSizeInBytes = 4096;
mAVCodecContext->sample_rate = 16000;mAVCodecContext->channel_layout = AV_CH_LAYOUT_STEREO;
int sampling_frequency_index = 8;
demo下载地址:http://www.demodashi.com/demo/10512.html
更多相关文章
- emoji表情在web html上显示
- Android(安卓)String.xml文件中转义字符的使用
- [多媒体]android MediaRecoder 实现录像机
- Android录音时指针摆动的实现(附源码)
- 庆祝一下,Android视频采集+H264编码成功
- Android(安卓)FFmpeg系列——2 播放音频
- Android麦克风录音的实现
- [置顶] Android(安卓)编码规范 | 代码风格指南
- android studio 中的编码问题
随机推荐
- Android自动化测试Espresso+UIAutomator
- Android_notifyDataSetChanged()
- Android(安卓)中文API (91) —— GestureDe
- Android使用Retrofit技术仿微信图片上传,
- 关于android:screenOrientation="portrai
- 在android 2.3.x中修改actionbarsherlock
- Android(安卓)HandlerThread分析
- Android(安卓)中加载延时加载动画
- 文件下载cordovaFileTransfer:cordova.fil
- android:persistent="true"相关--保证不