Android使用FFmpeg(五)--ffmpeg實現音頻播放(使用openSL ES進行播放)

關於

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實現暫停、快退快進播放android

準備工做

openSL ES瞭解git

正文

實現總體思路:使用ffmpeg解封裝、解碼視頻獲得pcm數據-->數據添加到opensl es緩衝區中-->播放
實現具體思路:由於在上篇中詳細講解了將視頻經過ffmpeg解碼成pcm,因此在這篇中將詳細講解如何經過opensl es播放。github


咱們仍是根據流程圖來進行代碼的編寫,註釋已經寫好,代碼不懂之處能夠看註釋:
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.釋放資源,從下往上依次釋放:ui

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的音頻文件。
源碼地址spa

相關文章
相關標籤/搜索