美文网首页多媒体科技NDK开发
Android使用FFmpeg(五)--ffmpeg实现音频播放

Android使用FFmpeg(五)--ffmpeg实现音频播放

作者: 2012lc | 来源:发表于2017-11-17 10:44 被阅读1072次

    关于

    Android使用FFmpeg(一)--编译ffmpeg
    Android使用FFmpeg(二)--Android Studio配置ffmpeg
    Android使用FFmpeg(三)--ffmpeg实现视频播放
    Android使用FFmpeg(四)--ffmpeg实现音频播放(使用AudioTrack进行播放)
    Android使用FFmpeg(五)--ffmpeg实现音频播放(使用openSL ES进行播放)
    Android使用FFmpeg(六)--ffmpeg实现音视频同步播放
    Android使用FFmpeg(七)--ffmpeg实现暂停、快退快进播放

    准备工作

    openSL ES了解

    正文

    实现整体思路:使用ffmpeg解封装、解码视频得到pcm数据-->数据添加到opensl es缓冲区中-->播放
    实现具体思路:因为在上篇中详细讲解了将视频通过ffmpeg解码成pcm,所以在这篇中将详细讲解如何通过opensl es播放。

    openSLes实现大概流程图.png
    我们还是根据流程图来进行代码的编写,注释已经写好,代码不懂之处可以看注释:
    1.创建引擎:
    //创建引擎
    void createEngine(){
        slCreateEngine(&engineObject,0,NULL,0,NULL,NULL);//创建引擎
        (*engineObject)->Realize(engineObject,SL_BOOLEAN_FALSE);//实现engineObject接口对象
        (*engineObject)->GetInterface(engineObject,SL_IID_ENGINE,&engineEngine);//通过引擎调用接口初始化SLEngineItf
    }
    

    2.创建混音器:

    //创建混音器
    void createMixVolume(){
        (*engineEngine)->CreateOutputMix(engineEngine,&outputMixObject,0,0,0);//用引擎对象创建混音器接口对象
        (*outputMixObject)->Realize(outputMixObject,SL_BOOLEAN_FALSE);//实现混音器接口对象
        SLresult   sLresult = (*outputMixObject)->GetInterface(outputMixObject,SL_IID_ENVIRONMENTALREVERB,&outputMixEnvironmentalReverb);//利用混音器实例对象接口初始化具体的混音器对象
        //设置
        if (SL_RESULT_SUCCESS == sLresult) {
            (*outputMixEnvironmentalReverb)->
                    SetEnvironmentalReverbProperties(outputMixEnvironmentalReverb, &settings);
        }
    }
    
    

    3.创建播放器并播放:

    void createPlayer(){
        //初始化ffmpeg
        int rate;
        int channels;
        createFFmpeg(&rate,&channels);
        LOGE("RATE %d",rate);
        LOGE("channels %d",channels);
        /*
         * typedef struct SLDataLocator_AndroidBufferQueue_ {
        SLuint32    locatorType;//缓冲区队列类型
        SLuint32    numBuffers;//buffer位数
    } */
    
        SLDataLocator_AndroidBufferQueue android_queue = {SL_DATALOCATOR_ANDROIDSIMPLEBUFFERQUEUE,2};
        /**
        typedef struct SLDataFormat_PCM_ {
            SLuint32        formatType;  pcm
            SLuint32        numChannels;  通道数
            SLuint32        samplesPerSec;  采样率
            SLuint32        bitsPerSample;  采样位数
            SLuint32        containerSize;  包含位数
            SLuint32        channelMask;     立体声
            SLuint32        endianness;    end标志位
        } SLDataFormat_PCM;
         */
        SLDataFormat_PCM pcm = {SL_DATAFORMAT_PCM,channels,rate*1000
                ,SL_PCMSAMPLEFORMAT_FIXED_16
                ,SL_PCMSAMPLEFORMAT_FIXED_16
                ,SL_SPEAKER_FRONT_LEFT|SL_SPEAKER_FRONT_RIGHT,SL_BYTEORDER_LITTLEENDIAN};
    
        /*
         * typedef struct SLDataSource_ {
                void *pLocator;//缓冲区队列
                void *pFormat;//数据样式,配置信息
            } SLDataSource;
         * */
        SLDataSource dataSource = {&android_queue,&pcm};
    
    
        SLDataLocator_OutputMix slDataLocator_outputMix={SL_DATALOCATOR_OUTPUTMIX,outputMixObject};
    
    
        SLDataSink slDataSink = {&slDataLocator_outputMix,NULL};
    
    
        const SLInterfaceID ids[3]={SL_IID_BUFFERQUEUE,SL_IID_EFFECTSEND,SL_IID_VOLUME};
        const SLboolean req[3]={SL_BOOLEAN_FALSE,SL_BOOLEAN_FALSE,SL_BOOLEAN_FALSE};
    
        /*
         * SLresult (*CreateAudioPlayer) (
            SLEngineItf self,
            SLObjectItf * pPlayer,
            SLDataSource *pAudioSrc,//数据设置
            SLDataSink *pAudioSnk,//关联混音器
            SLuint32 numInterfaces,
            const SLInterfaceID * pInterfaceIds,
            const SLboolean * pInterfaceRequired
        );
         * */
        LOGE("执行到此处")
        (*engineEngine)->CreateAudioPlayer(engineEngine,&audioplayer,&dataSource,&slDataSink,3,ids,req);
        (*audioplayer)->Realize(audioplayer,SL_BOOLEAN_FALSE);
        LOGE("执行到此处2")
        (*audioplayer)->GetInterface(audioplayer,SL_IID_PLAY,&slPlayItf);//初始化播放器
        //注册缓冲区,通过缓冲区里面 的数据进行播放
        (*audioplayer)->GetInterface(audioplayer,SL_IID_BUFFERQUEUE,&slBufferQueueItf);
        //设置回调接口
        (*slBufferQueueItf)->RegisterCallback(slBufferQueueItf,getQueueCallBack,NULL);
        //播放
        (*slPlayItf)->SetPlayState(slPlayItf,SL_PLAYSTATE_PLAYING);
    
        //开始播放
        getQueueCallBack(slBufferQueueItf,NULL);
    }
    //回调的函数
    void getQueueCallBack(SLAndroidSimpleBufferQueueItf  slBufferQueueItf, void* context){
    
        buffersize=0;
    
        getPcm(&buffer,&buffersize);
        if(buffer!=NULL&&buffersize!=0){
            //将得到的数据加入到队列中
            (*slBufferQueueItf)->Enqueue(slBufferQueueItf,buffer,buffersize);
        }
    }
    

    4.释放资源,从下往上依次释放:

    void realseResource(){
        if(audioplayer!=NULL){
            (*audioplayer)->Destroy(audioplayer);
            audioplayer=NULL;
            slBufferQueueItf=NULL;
            slPlayItf=NULL;
        }
        if(outputMixObject!=NULL){
            (*outputMixObject)->Destroy(outputMixObject);
            outputMixObject=NULL;
            outputMixEnvironmentalReverb=NULL;
        }
        if(engineObject!=NULL){
            (*engineObject)->Destroy(engineObject);
            engineObject=NULL;
            engineEngine=NULL;
        }
        realseFFmpeg();
    }
    

    5.关于使用ffmpeg得到pcm,我们将其单独写入一个cpp文件中,分为三部分:创建,得到pcm,释放资源。方便在openSL ES这边进行一个调用。具体代码的注释和思路在上篇中已经讲解,不懂的请看上篇的内容。这里的代码只是将整体的代码进行了分类和模块化。

    //
    // Created by david on 2017/9/25.
    //
    
    #include "FFmpegMusic.h"
    
    AVFormatContext *pFormatCtx;
    AVCodecContext *pCodecCtx;
    AVCodec *pCodex;
    AVPacket *packet;
    AVFrame *frame;
    SwrContext *swrContext;
    uint8_t *out_buffer;
    int out_channer_nb;
    int audio_stream_idx=-1;
    //opensl es调用 int * rate,int *channel
    int createFFmpeg(int *rate,int *channel){
        av_register_all();
        char *input = "/sdcard/input.mp3";
        pFormatCtx = avformat_alloc_context();
        LOGE("Lujng %s",input);
        LOGE("xxx %p",pFormatCtx);
        int error;
        char buf[] = "";
        //打开视频地址并获取里面的内容(解封装)
        if (error = avformat_open_input(&pFormatCtx, input, NULL, NULL) < 0) {
            av_strerror(error, buf, 1024);
            // LOGE("%s" ,inputPath)
            LOGE("Couldn't open file %s: %d(%s)", input, error, buf);
            // LOGE("%d",error)
            LOGE("打开视频失败")
        }
        //3.获取视频信息
        if(avformat_find_stream_info(pFormatCtx,NULL) < 0){
            LOGE("%s","获取视频信息失败");
            return -1;
        }
    
    
    
        int i=0;
        for (int i = 0; i < pFormatCtx->nb_streams; ++i) {
            if (pFormatCtx->streams[i]->codec->codec_type == AVMEDIA_TYPE_AUDIO) {
                LOGE("  找到音频id %d", pFormatCtx->streams[i]->codec->codec_type);
                audio_stream_idx=i;
                break;
            }
        }
    // mp3的解码器
    
    //    获取音频编解码器
        pCodecCtx=pFormatCtx->streams[audio_stream_idx]->codec;
        LOGE("获取视频编码器上下文 %p  ",pCodecCtx);
    
        pCodex = avcodec_find_decoder(pCodecCtx->codec_id);
        LOGE("获取视频编码 %p",pCodex);
    
        if (avcodec_open2(pCodecCtx, pCodex, NULL)<0) {
        }
        packet = (AVPacket *)av_malloc(sizeof(AVPacket));
    //    av_init_packet(packet);
    //    音频数据
    
        frame = av_frame_alloc();
    
    //    mp3  里面所包含的编码格式   转换成  pcm   SwcContext
        swrContext = swr_alloc();
    
        int length=0;
        int got_frame;
    //    44100*2
        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 = pCodecCtx->sample_rate;
    
    
        swr_alloc_set_opts(swrContext, out_ch_layout, out_formart, out_sample_rate,
                           pCodecCtx->channel_layout, pCodecCtx->sample_fmt, pCodecCtx->sample_rate, 0,
                           NULL);
    
        swr_init(swrContext);
    //    获取通道数  2
        out_channer_nb = av_get_channel_layout_nb_channels(AV_CH_LAYOUT_STEREO);
        *rate = pCodecCtx->sample_rate;
        *channel = pCodecCtx->channels;
        return 0;
    }
    //
    int getPcm(void **pcm,size_t *pcm_size){
        int frameCount=0;
        int got_frame;
        while (av_read_frame(pFormatCtx, packet) >= 0) {
            if (packet->stream_index == audio_stream_idx) {
    //            解码  mp3   编码格式frame----pcm   frame
                avcodec_decode_audio4(pCodecCtx, frame, &got_frame, packet);
                if (got_frame) {
                    LOGE("解码");
                    /**
                     * int swr_convert(struct SwrContext *s, uint8_t **out, int out_count,
                                    const uint8_t **in , int in_count);
                     */
                    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);
                    *pcm = out_buffer;
                    *pcm_size = size;
                    break;
                }
            }
        }
        return 0;
    }
    void realseFFmpeg(){
        av_free_packet(packet);
        av_free(out_buffer);
        av_frame_free(&frame);
        swr_free(&swrContext);
        avcodec_close(pCodecCtx);
        avformat_close_input(&pFormatCtx);
    }
    
    

    小结

    按照流程图来编写代码清晰明朗,只是一些细节的地方注意就行。另外注意以下几点:
    1.测试时请使用真机测试,因为没有使用x86的cpu的.so;
    2.注意添加权限;
    3.注意将music.cpp添加到CMakeLists.txt中;
    4.测试时请在手机中存入input.xxx的音频文件。
    源码地址

    相关文章

      网友评论

      • 风舞尘起:您好,请教下,有没有什么库可以实现40秒左右mp3循环播放,中间无停顿的效果,MediaPlayer 的setLooping(true),中间会有比较明显的停顿

      本文标题:Android使用FFmpeg(五)--ffmpeg实现音频播放

      本文链接:https://www.haomeiwen.com/subject/towpmxtx.html