FFmpeg_Android音频播放demo--audioTrack方式

        在之前的文章中,我们有讲解了FFmpeg的音频的解码流程:FFmpeg音频解码流程详解,本文于此基础上,讲解在Android平台上对mp3文件进行解码并播放。本文例子使用AudioTrack来对音频解码后的数据进行播放。

一、音频解码播放流程图

        与音频的解码流程基本一致,只是最终不是保存为文件,而是回调播放:

 二、音频解码播放整体流程

1、注册各大组件

        先注册ffmpeg相关的各大组件的

    //注册各大组件
    av_register_all();
    LOGE("注册成功")

2、打开音频文件并获取相关上下文

        在解码播放之前我们得获取里面的内容,这一步就是打开地址并且获取里面的内容。其中avFormatContext是内容的一个上下文。

        并使用avformat_open_input打开播放源,inputPath为输入的地址,也就是音频文件,然后使用avformat_find_stream_info从获取的内容中寻找相关流。

    AVFormatContext *avFormatContext = avformat_alloc_context();//获取上下文
    int error;
    //打开音频地址并获取里面的内容(解封装)
    error = avformat_open_input(&avFormatContext, inputPath, NULL, NULL);
    if (error < 0){
        LOGE("打开音频文件失败\n");
        return;
    }
    if (avformat_find_stream_info(avFormatContext, NULL) < 0){
        LOGE("获取内容失败")
        return;
    }

3、寻找音频流

        我们在上面已经获取了内容,我们再从中找出相对应的音频流。

    //获取音频的编码信息
    int mAudioStreamIdx = -1;
    for (int i = 0; i < avFormatContext->nb_streams; ++i) {
        if (avFormatContext->streams[i]->codec->codec_type == AVMEDIA_TYPE_AUDIO) {
            LOGE("  找到音频id %d", avFormatContext->streams[i]->codec->codec_type);
            mAudioStreamIdx=i;
            break;
        }
    }

4、获取并打开解码器

        如果要进行解码,那么得有解码器并打开解码器。在这一步中我加了个过滤器的相关配置,这个东西不是非必要的,所以我加了个宏ABSFILTER_ENABLE可以选择是否打开。

    // 寻找解码器 {start
    //获取解码器上下文
    AVCodecContext *mAvContext=avFormatContext->streams[mAudioStreamIdx]->codec;
    //获取解码器
    AVCodec *mAcodec = NULL;
    mAcodec = avcodec_find_decoder(mAvContext->codec_id);

#if ABSFILTER_ENABLE
    //过滤器相关配置,这个与音频码流格式相关,也可以不用
    const AVBitStreamFilter * absFilter = NULL;
    AVBSFContext *absCtx = NULL;
    AVCodecParameters *codecpar = NULL;
    absFilter = av_bsf_get_by_name("mp3decomp");
    //过滤器分配内存
    av_bsf_alloc(absFilter, &absCtx);
    //添加解码器属性
    codecpar = avFormatContext->streams[mAudioStreamIdx]->codecpar;
    avcodec_parameters_copy(absCtx->par_in, codecpar);
    absCtx->time_base_in = avFormatContext->streams[mAudioStreamIdx]->time_base;
    //初始化过滤器上下文
    av_bsf_init(absCtx);
#endif

    // 打开解码器
    if (avcodec_open2(mAvContext, mAcodec, NULL) != 0){
        LOGE("打开失败")
        return;
    }
    LOGE("解码器打开成功")
    // 寻找解码器 end}

5、申请AVPacket和AVFrame以及相关设置

        申请AVPacket和AVFrame,其中AVPacket的作用是:保存解码之前的数据和一些附加信息等;AVFrame的作用是:存放解码过后的数据。

    //申请AVPacket
    AVPacket *packet = (AVPacket *) av_malloc(sizeof(AVPacket));
    av_init_packet(packet);
    //申请AVFrame
    AVFrame *frame = av_frame_alloc();//分配一个AVFrame结构体,AVFrame结构体一般用于存储原始数据,指向解码后的原始帧

6、初始化SwrContext,进行重采样

        配置解码输出的数据的重采样,这一步可以根据自己的需求对输出进行一个重采样的设置,也可以不要采用默认。一般会配置。

    //得到SwrContext ,进行重采样 {start
    SwrContext *swrContext = swr_alloc();
    //缓存区
    uint8_t *out_buffer = (uint8_t *) av_malloc(44100 * 2);
    //输出的声道布局(立体声)
    uint64_t  out_ch_layout=AV_CH_LAYOUT_STEREO;
    //输出采样位数  16位
    enum AVSampleFormat out_formart=AV_SAMPLE_FMT_S16;
    //输出的采样率必须与输入相同
    int out_sample_rate = mAvContext->sample_rate;
    //swr_alloc_set_opts将PCM源文件的采样格式转换为自己希望的采样格式
    swr_alloc_set_opts(swrContext, out_ch_layout, out_formart, out_sample_rate,
                       mAvContext->channel_layout, mAvContext->sample_fmt, mAvContext->sample_rate, 0,
                       NULL);
    swr_init(swrContext);
    LOGE("设置重采样成功")
    //end}

7、设置好与java层的反射回调

        因为我们是使用java层的audioTrack进行音频数据播放的,因此在JNI层需要先设置好与java层接口的反射相关:

    //获取通道数  2
    int out_channer_nb = av_get_channel_layout_nb_channels(AV_CH_LAYOUT_STEREO);
    //反射得到Class类型
    jclass david_player = env->GetObjectClass(thiz);
    //反射得到createAudio方法
    jmethodID createAudio = env->GetMethodID(david_player, "createTrack", "(II)V");
    //反射调用createAudio
    env->CallVoidMethod(thiz, createAudio, 44100, out_channer_nb);
    //反射得到playTrack方法
    jmethodID audio_write = env->GetMethodID(david_player, "playTrack", "([BI)V");

       jni通过调用java层的audiotrack方法来实现播放, java层的播放代码是:

    private AudioTrack audioTrack;
    //    这个方法  是C进行调用  通道数
    public void createTrack(int sampleRateInHz,int nb_channals) {

        int channaleConfig;//通道数
        if (nb_channals == 1) {
            channaleConfig = AudioFormat.CHANNEL_OUT_MONO;
        } else if (nb_channals == 2) {
            channaleConfig = AudioFormat.CHANNEL_OUT_STEREO;
        }else {
            channaleConfig = AudioFormat.CHANNEL_OUT_MONO;
        }
        int buffersize=AudioTrack.getMinBufferSize(sampleRateInHz,
                channaleConfig, AudioFormat.ENCODING_PCM_16BIT);
        audioTrack = new AudioTrack(AudioManager.STREAM_MUSIC,sampleRateInHz,channaleConfig,
                AudioFormat.ENCODING_PCM_16BIT,buffersize,AudioTrack.MODE_STREAM);
        audioTrack.play();
    }

    //C传入音频数据
    public void playTrack(byte[] buffer, int lenth) {
        if (audioTrack != null && audioTrack.getPlayState() == AudioTrack.PLAYSTATE_PLAYING) {
            audioTrack.write(buffer, 0, lenth);
        }
    }

8、开始解码播放

        与音频解码核心段代码一致,只不过最终拿到的不是保存进文件,而是通过回调java层audioTrack接口进行播放。

        另外这里有用了上面所说的过滤器的东西,我们暂时先不关心。

        完整过程如下:

    while(1)
    {
        int ret = av_read_frame(avFormatContext, packet);
        if (ret != 0){
            av_strerror(ret,buf,sizeof(buf));
            LOGE("--%s--\n",buf);
            av_packet_unref(packet);
            break;
        }


        if (ret >= 0 && packet->stream_index != mAudioStreamIdx){
            av_packet_unref(packet);
            continue;
        }

#if ABSFILTER_ENABLE
        if (av_bsf_send_packet(absCtx, packet) < 0){
            LOGE("av_bsf_send_packet faile \n");
            av_packet_unref(packet);
            continue;
        }
        if (av_bsf_receive_packet(absCtx, packet) < 0) {
            LOGE("av_bsf_receive_packet faile \n");
            av_packet_unref(packet);
            continue;
        }
#endif
        {
            // 发送待解码包
            int result = avcodec_send_packet(mAvContext, packet);
            av_packet_unref(packet);
            if (result < 0){
                av_log(NULL, AV_LOG_ERROR, "Error submitting a packet for decoding\n");
                continue;
            }

            // 接收解码数据
            while (result >= 0){
                result = avcodec_receive_frame(mAvContext, frame);
                if (result == AVERROR_EOF)
                    break;
                else if (result == AVERROR(EAGAIN)){
                    result = 0;
                    break;
                }
                else if (result < 0){
                    av_log(NULL, AV_LOG_ERROR, "Error decoding frame\n");
                    av_frame_unref(frame);
                    break;
                }

                LOGE("解码播放")
                swr_convert(swrContext, &out_buffer, 44100 * 2, (const uint8_t **) frame->data, frame->nb_samples);
                //缓冲区的大小
                int size = av_samples_get_buffer_size(NULL, out_channer_nb, frame->nb_samples,
                                                      AV_SAMPLE_FMT_S16, 1);
                jbyteArray audio_sample_array = env->NewByteArray(size);
                env->SetByteArrayRegion(audio_sample_array, 0, size, (const jbyte *) out_buffer);
                env->CallVoidMethod(thiz, audio_write, audio_sample_array, size);
                env->DeleteLocalRef(audio_sample_array);

                av_frame_unref(frame);
            }
        }

    }

9、收尾释放

        最后释放相关资源

    swr_free(&swrContext);
    av_frame_free(&frame);
    avcodec_close(mAvContext);
    avformat_free_context(avFormatContext);
#if ABSFILTER_ENABLE
    av_bsf_free(&absCtx);
    absCtx = NULL;
#endif
    env->ReleaseStringUTFChars(input, inputPath);

        至此,mp3文件就可以解码,并通过audioTrack对解码后的数据进行播放。

        java层的audiotrack方法最终是通过调用底层的openesl es来进行播放的,相当于绕了一个圈,在下篇文章中我们将实现直接使用opensl es来播放音频。

        FFmpeg_Android音频播放demo--openSLES方式

三、demo运行

        demo中指定了播放的文件是/sdcard/input.mp3,如下代码,若要改文件,可以在此处修改:

findViewById(R.id.btnStartVideo).setOnClickListener(new View.OnClickListener() {
	@Override
	public void onClick(View view) {
		String PATH = Environment.getExternalStorageDirectory().getPath();
		String input = PATH + File.separator + "input.mp3";
		musicPlay.playSound(input);
	}
});

        运行后截图如下:

        点击“PLAY MUSIC”按钮进行播放,可以听到音乐,说明demo运行正常。

        过程中log如下:

        完整例子已经放到github上,如下:

https://github.com/weekend-y/FFmpeg_Android_Demo/tree/master/demo8_byAudioTrack

猜你喜欢

转载自blog.csdn.net/weekend_y45/article/details/125216865