Android 音視頻深入 十四 FFmpeg與OpenSL ES 播放mp3音樂,能暫停(附源碼下載)

来源:https://www.cnblogs.com/jianpanwuzhe/archive/2018/02/26/8472241.html
-Advertisement-
Play Games

項目地址https://github.com/979451341/FFmpegOpenslES 這次說的是FFmpeg解碼mp3,數據給OpenSL ES播放,並且能夠暫停。 1.創建引擎 2.創建混音器 3.FFmpeg解碼mp3準備工作 4.緩存隊列設置 最後還要給這個緩存回調函數賦予參數,這個 ...


項目地址
https://github.com/979451341/FFmpegOpenslES

這次說的是FFmpeg解碼mp3,數據給OpenSL ES播放,並且能夠暫停。

1.創建引擎

    slCreateEngine(&engineObject,0,NULL,0,NULL,NULL);//創建引擎
    (*engineObject)->Realize(engineObject,SL_BOOLEAN_FALSE);//實現engineObject介面對象
    (*engineObject)->GetInterface(engineObject,SL_IID_ENGINE,&engineEngine);//通過引擎調用介面初始化SLEngineItf

2.創建混音器

    (*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.FFmpeg解碼mp3準備工作

    av_register_all();
    char *input = "/storage/emulated/0/pauseRecordDemo/video/a.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;

4.緩存隊列設置

    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);

 

最後還要給這個緩存回調函數賦予參數,這個回調函數主要負責提供FFmpeg解碼出的數據

    //開始播放
    getQueueCallBack(slBufferQueueItf,NULL);

 

我們再來看看這個函數說的啥,靠Enqueue函數把數據放入隊列里,這個數據則是從getPcm函數得到的

void getQueueCallBack(SLAndroidSimpleBufferQueueItf  slBufferQueueItf, void* context){
    buffersize=0;
    getPcm(&buffer,&buffersize);
    if(buffer!=NULL&&buffersize!=0){
        //將得到的數據加入到隊列中
        (*slBufferQueueItf)->Enqueue(slBufferQueueItf,buffer,buffersize);
    }
}

 

這個FFmpeg解碼mp3得到Pcm數據,這個主要是每解碼出一個packet數據,就跳出迴圈,將數據給上層函數壓入隊列,當隊列的數據讀取完了,又會調用getQueueCallBack函數再來獲取FFmpeg解碼出的pcm數據

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;
}

 

5.播放音樂

    (*slPlayItf)->SetPlayState(slPlayItf,SL_PLAYSTATE_PLAYING);

 

6.暫停音樂

    (*slPlayItf)->SetPlayState(slPlayItf, SL_PLAYSTATE_PAUSED);

7.釋放資源

首先釋放關於OpenSL ES的實體

    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;
    }

 

然後釋放FFmpeg占用的資源

    av_free_packet(packet);
    av_free(out_buffer);
    av_frame_free(&frame);
    swr_free(&swrContext);
    avcodec_close(pCodecCtx);
    avformat_close_input(&pFormatCtx);

您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • MySQL無法重啟、無法關閉、無法啟動、無法使用,如果是因為修改了主機名,可以這樣解決:關閉掉所有mysql進程,然後在啟動一些mysql! ...
  • IDENTITY_INSERT 為 ON 時 , 必須把需要插入的列名列出來 不然報錯 正確例子: SET IDENTITY_INSERT table(表名) ONinsert into table(id,name) value(1,名稱) SET IDENTITY_INSERT table OFF ...
  • 大家會發現一個空項目,從手機桌面打開app是秒啟動。但是對於自己開發的項目,有時會發現打開app的時候,會有短暫的1秒--2秒的白屏或者黑屏,然後才進入到程式界面。 個人理解為我們自己實現的Application文件裡面做了較多的初始化操作,當這些初始化操作完成後才進入到第一個Activity,這段 ...
  • 最近做了一個android項目用到編解碼功能。大概需求是:通過攝像頭拍攝一段視頻,然後抽幀,生成一個短視頻,以及倒序視頻,剛開始直接用 H.264 編碼格式,沒有使用MP4容器封裝。做了這些功能後,反而覺得使用MP4格式更加相容各機型,減少BUG出現。舉個明顯例子:在Android硬編的時候,常常會 ...
  • 新建基於UIView擴展類 UIView+wkjFrame,此類是為了方便獲取一些基於UIView類UI控制項的坐標和位置的簡化,直接引用即可 UIView+wkjFrame.h UIView+wkjFrame.m ...
  • 前言 2018年2月26日 農曆正月十一 星期一 今天就想更新下博客,內容不多,心情複雜; 突然想吟詩一首: 其實,我是一個善良的人; 其實,我是一枚... 算了,還是說正事吧 消除CocoaPods警告 在我們集成了CocoaPods 並 install 後,有些集成後的內容會有很多警告,其實只需 ...
  • 1關於圖片選擇器的重點的地方上一篇已經寫了,主要就是如何獲取手機圖片的問題,至於仿微信,主要就是佈局的問題了。 2年前的時候自己就斷斷續續的寫這個,本來以為是比較好寫的,但是越寫發現微信的細節越多,之前有點考慮不對的地方改來改去,再加上我自己不會切圖,能用代碼畫的基本上都用代碼畫出來的,基本沒用圖片 ...
  • 博客首頁:http://www.cnblogs.com/kezhuang/p/ 本篇文章來分析一下WindowManager的後續工作,也就是ViewRootImpl的setView函數的工作 本篇內容很簡單,僅僅是個大繪製流程的初始化工作,接下來會著重分析performTraversals函數,在 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...