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