android端基于FFmpeg的音频解码与两种播放方式

FFmpeg在音视频开发的地位不必多说,它已经是行业的一个品牌标杆。本篇文章探讨使用FFmpeg进行音频解码,然后反射调用android系统自带的AudioTrack和OpenSL ES两种播放方式。

首先谈下FFmpeg解码流程,步骤包括:注册组件、分配FormatContext、打开音频文件、获取输入文件信息、获取音频流索引位置、获取音频解码器、打开解码器、循环读取待解码数据、解码完一帧送去播放器播放。代码如下:

	//注册组件
	av_register_all();
	AVFormatContext *pFormatCtx = avformat_alloc_context();
	//打开音频文件
	if(avformat_open_input(&pFormatCtx,input_cstr,NULL,NULL) != 0){
		LOGI("%s","无法打开音频文件");
		return;
	}
	//获取输入文件信息
	if(avformat_find_stream_info(pFormatCtx,NULL) < 0){
		LOGI("%s","无法获取输入文件信息");
		return;
	}
	//获取音频流索引位置
	int i = 0, audio_stream_idx = -1;
	for(; i < pFormatCtx->nb_streams;i++){
		if(pFormatCtx->streams[i]->codec->codec_type == AVMEDIA_TYPE_AUDIO){
			audio_stream_idx = i;
			break;
		}
	}

	//获取音频解码器
	AVCodecContext *codecCtx = pFormatCtx->streams[audio_stream_idx]->codec;
	AVCodec *codec = avcodec_find_decoder(codecCtx->codec_id);
	if(codec == NULL){
		LOGI("%s","无法获取解码器");
		return;
	}
	//打开解码器
	if(avcodec_open2(codecCtx,codec,NULL) < 0){
		LOGI("%s","无法打开解码器");
		return;
	}
	//压缩数据
	AVPacket *packet = (AVPacket *)av_malloc(sizeof(AVPacket));
	//解压缩数据
	AVFrame *frame = av_frame_alloc();
	//frame->16bit 44100 PCM 统一音频采样格式与采样率
	SwrContext *swrCtx = swr_alloc();

	//输入的采样格式
	enum AVSampleFormat in_sample_fmt = codecCtx->sample_fmt;
	//输出采样格式16bit PCM
	enum AVSampleFormat out_sample_fmt = AV_SAMPLE_FMT_S16;
	//输入采样率
	int in_sample_rate = codecCtx->sample_rate;
	//输出采样率
	int out_sample_rate = in_sample_rate;
	//声道布局(2个声道,默认立体声stereo)
	uint64_t in_ch_layout = codecCtx->channel_layout;
	//输出的声道布局(立体声)
	uint64_t out_ch_layout = AV_CH_LAYOUT_STEREO;
	swr_alloc_set_opts(swrCtx,
					   out_ch_layout,out_sample_fmt,out_sample_rate,
					   in_ch_layout,in_sample_fmt,in_sample_rate,
					   0, NULL);
	swr_init(swrCtx);
	//输出的声道个数
	int out_channel_nb = av_get_channel_layout_nb_channels(out_ch_layout);
	uint8_t *out_buffer = (uint8_t *)av_malloc(MAX_AUDIO_FRAME_SIZE);
	int got_frame = 0,index = 0, ret;
	//不断读取编码数据
	while(av_read_frame(pFormatCtx,packet) >= 0){
		//解码音频类型的Packet
		if(packet->stream_index == audio_stream_idx){
			//解码
			ret = avcodec_decode_audio4(codecCtx,frame,&got_frame,packet);
			if(ret < 0){
				break;
			}
			//解码一帧成功
			if(got_frame > 0){
				......
			}
		}
		//释放AVPacket
		av_free_packet(packet);
	}
每当解码一帧音频数据成功后,调用音频播放器去播放,可以反射调用android系统的AudioTrack进行播放,也可以调用android内嵌的OpenSLES(Open Sound Library Embeded System)播放音频。

一、AudioTrack

1、在Java层提供获取AudioTrack对象的方法:

/**
     * 创建一个AudioTrack对象
     * @param sampleRate 采样率
     * @param channels 声道布局
     * @return AudioTrack
     */
    public AudioTrack createAudioTrack(int sampleRate, int channels){
        int audioFormat = AudioFormat.ENCODING_PCM_16BIT;
        int channelConfig;
        if(channels == 1){
            channelConfig = android.media.AudioFormat.CHANNEL_OUT_MONO;
        }else if(channels == 2){
            channelConfig = android.media.AudioFormat.CHANNEL_OUT_STEREO;
        }else{
            channelConfig = android.media.AudioFormat.CHANNEL_OUT_STEREO;
        }

        int bufferSizeInBytes = AudioTrack.getMinBufferSize(sampleRate, channelConfig, audioFormat);

        return new AudioTrack(AudioManager.STREAM_MUSIC, sampleRate, channelConfig, audioFormat,
                bufferSizeInBytes, AudioTrack.MODE_STREAM);
    }
2、Native层反射调用Java方法:

	jclass player_class = (*env)->GetObjectClass(env,jthiz);
    if(!player_class){
        LOGE("player_class not found...");
    }
	//AudioTrack对象
	jmethodID audio_track_method = (*env)->GetMethodID(env,player_class,"createAudioTrack","(II)Landroid/media/AudioTrack;");
    if(!audio_track_method){
        LOGE("audio_track_method not found...");
    }
	jobject audio_track = (*env)->CallObjectMethod(env,jthiz,audio_track_method,out_sample_rate,out_channel_nb);

	//调用play方法
	jclass audio_track_class = (*env)->GetObjectClass(env,audio_track);
	jmethodID audio_track_play_mid = (*env)->GetMethodID(env,audio_track_class,"play","()V");
	(*env)->CallVoidMethod(env,audio_track,audio_track_play_mid);

	//获取write()方法
	jmethodID audio_track_write_mid = (*env)->GetMethodID(env,audio_track_class,"write","([BII)I");
3、调用AudioTrack的write方法播放

//音频格式转换
	swr_convert(swrCtx, &out_buffer, MAX_AUDIO_FRAME_SIZE,(const uint8_t **)frame->data,frame->nb_samples);
	int out_buffer_size = av_samples_get_buffer_size(NULL, out_channel_nb,
			frame->nb_samples, out_sample_fmt, 1);

	jbyteArray audio_sample_array = (*env)->NewByteArray(env,out_buffer_size);
	jbyte* sample_byte_array = (*env)->GetByteArrayElements(env,audio_sample_array,NULL);
	//拷贝缓冲数据
	memcpy(sample_byte_array, out_buffer, (size_t) out_buffer_size);
	//释放数组
	(*env)->ReleaseByteArrayElements(env,audio_sample_array,sample_byte_array,0);
	//调用AudioTrack的write方法进行播放
	(*env)->CallIntMethod(env,audio_track,audio_track_write_mid,
			audio_sample_array,0,out_buffer_size);
	//释放局部引用
	(*env)->DeleteLocalRef(env,audio_sample_array);
	usleep(1000 * 16);

二、OpenSL ES开源音频库播放
使用OpenSL ES开源库,操作步骤相对多些,主要步骤包括:创建OpenSL ES引擎、获取引擎接口、创建带有缓冲区的音频播放器、获取缓冲队列接口、注册音频播放器回调函数、调用SetPlayState方法启动播放音乐、解码数据入队列等待播放。

1、创建OpenSL ES引擎

//创建OpenSLES引擎
void createEngine() {
    SLresult result;
    //创建引擎
    result = slCreateEngine(&engineObject, 0, NULL, 0, NULL, NULL);
    LOGI("slCreateEngine=%d", result);
    result = (*engineObject)->Realize(engineObject, SL_BOOLEAN_FALSE);
    LOGI("engineObject->Realize=%d", result);
    //获取引擎接口
    result = (*engineObject)->GetInterface(engineObject, SL_IID_ENGINE, &engineEngine);
    LOGI("engineObject->GetInterface=%d", result);
    //创建输出混音器
    result = (*engineEngine)->CreateOutputMix(engineEngine, &outputMixObject, 0, 0, 0);
    LOGI("CreateOutputMix=%d", result);
    //关联输出混音器
    result = (*outputMixObject)->Realize(outputMixObject, SL_BOOLEAN_FALSE);
    LOGI("outputMixObject->Realize=%d", result);
    //获取reverb接口
    result = (*outputMixObject)->GetInterface(outputMixObject, SL_IID_ENVIRONMENTALREVERB,
                                              &outputMixEnvironmentalReverb);
    LOGI("outputMixObject->GetInterface=%d", result);
    if (SL_RESULT_SUCCESS == result) {
        result = (*outputMixEnvironmentalReverb)->SetEnvironmentalReverbProperties(
                outputMixEnvironmentalReverb, &reverbSettings);
    }
    LOGI("SetEnvironmentalReverbProperties=%d", result);
}
2、创建音频播放器

//创建带有缓冲队列的音频播放器
void createBufferQueueAudioPlayer(int rate, int channel, int bitsPerSample) {
    SLresult result;

    //配置音频源
    SLDataLocator_AndroidSimpleBufferQueue buffer_queue = {SL_DATALOCATOR_ANDROIDSIMPLEBUFFERQUEUE, 2};
    SLDataFormat_PCM format_pcm;
    format_pcm.formatType = SL_DATAFORMAT_PCM;
    format_pcm.numChannels = (SLuint32) channel;
    format_pcm.bitsPerSample = (SLuint32) bitsPerSample;
    format_pcm.samplesPerSec = (SLuint32) (rate * 1000);
    format_pcm.containerSize = 16;
    if (channel == 2)
        format_pcm.channelMask = SL_SPEAKER_FRONT_LEFT | SL_SPEAKER_FRONT_RIGHT;
    else
        format_pcm.channelMask = SL_SPEAKER_FRONT_CENTER;
    format_pcm.endianness = SL_BYTEORDER_LITTLEENDIAN;
    SLDataSource audioSrc = {&buffer_queue, &format_pcm};

    //配置音频池
    SLDataLocator_OutputMix loc_outmix = {SL_DATALOCATOR_OUTPUTMIX, outputMixObject};
    SLDataSink audioSnk = {&loc_outmix, NULL};

    //创建音频播放器
    const SLInterfaceID ids[3] = {SL_IID_BUFFERQUEUE, SL_IID_EFFECTSEND, SL_IID_VOLUME};
    const SLboolean req[3] = {SL_BOOLEAN_TRUE, SL_BOOLEAN_TRUE, SL_BOOLEAN_TRUE};
    result = (*engineEngine)->CreateAudioPlayer(engineEngine, &bqPlayerObject, &audioSrc, &audioSnk,
                                                3, ids, req);
    LOGI("CreateAudioPlayer=%d", result);

    //关联播放器
    result = (*bqPlayerObject)->Realize(bqPlayerObject, SL_BOOLEAN_FALSE);
    LOGI("bqPlayerObject Realize=%d", result);

    //获取播放接口
    result = (*bqPlayerObject)->GetInterface(bqPlayerObject, SL_IID_PLAY, &bqPlayerPlay);
    LOGI("GetInterface bqPlayerPlay=%d", result);

    //获取缓冲队列接口
    result = (*bqPlayerObject)->GetInterface(bqPlayerObject, SL_IID_BUFFERQUEUE,
                                             &bqPlayerBufferQueue);
    LOGI("GetInterface bqPlayerBufferQueue=%d", result);

    //注册缓冲队列回调
    result = (*bqPlayerBufferQueue)->RegisterCallback(bqPlayerBufferQueue, bqPlayerCallback, NULL);
    LOGI("RegisterCallback=%d", result);

    //获取音效接口
    result = (*bqPlayerObject)->GetInterface(bqPlayerObject, SL_IID_EFFECTSEND,
                                             &bqPlayerEffectSend);
    LOGI("GetInterface effect=%d", result);

    //获取音量接口
    result = (*bqPlayerObject)->GetInterface(bqPlayerObject, SL_IID_VOLUME, &bqPlayerVolume);
    LOGI("GetInterface volume=%d", result);

    //开始播放音乐
    result = (*bqPlayerPlay)->SetPlayState(bqPlayerPlay, SL_PLAYSTATE_PLAYING);
    LOGI("SetPlayState=%d", result);
}
3、解码数据回调给缓冲队列

void bqPlayerCallback(SLAndroidSimpleBufferQueueItf bufferQueueItf, void *context) {
    bufferSize = 0;
    getPCM(&buffer, &bufferSize);
    //如果buffer不为空,入待播放队列
    if (NULL != buffer && 0 != bufferSize) {
        SLresult result;
        result = (*bqPlayerBufferQueue)->Enqueue(bqPlayerBufferQueue, buffer, bufferSize);
        if(result < 0){
            LOGE("Enqueue error...");
        }
    }
}
好了,关于FFmpeg解码音频与AudioTrack、OpenSL ES播放调用介绍完毕。

源码在这里:https://github.com/xufuji456/FFmpegAndroid








发布了63 篇原创文章 · 获赞 179 · 访问量 18万+

猜你喜欢

转载自blog.csdn.net/u011686167/article/details/79223345