之前做的直播设计到音视频编码、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;
demo下载地址:http://www.demodashi.com/demo/10512.html