之前做的直播设计到音视频编码、rtmp推流、rtmp流播放等内容,现在抽时间整理一下。首先说一下音频编码(pcm 编码得到aac数据)。
开始录音
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); } @Override public 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"); } }
1 初始化编码器
//初始化ffmpeg 编码器 mFFAacEncoderJni.start();
FFAacEncoder代码如下
public class FFAacEncoder { private String TAG = "FFAacEncoder java"; //load .so static{ 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 URL if (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 Header avformat_write_header(mAVFormatContext, NULL); } return 0; }
//设置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表示8000 int 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; }
我这里录音
采样率为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;