1、ffmpeg 播放音視頻代碼流程
第一步 、 解封裝
1、avformat_alloc_context
初始化一個AVFormatContext 結構體
2、avformat_open_input(&avFormatContext, data_source, 0, &dictionary)
打開一個媒體源
3、avformat_find_stream_info(avFormatContext, 0)
找到媒體流的信息, 至此媒體文件的內容被封裝到了AVFormatContext 結構體中了;
4、avFormatContext->nb_streams
開啓遍歷,nb_streams代表AVFormatContext結構中的流的個數,一般有視頻流、音頻流 和字幕流
5、avFormatContext->streams[index_stream]
獲取到AVFormatContext結構體中的一個流
6、avcodec_find_decoder(avCodecParameters->codec_id)
通過編碼器id找到對應的流的編解碼器AVCodec
7、avcodec_alloc_context3(avCodec)
通過一個編解碼器初始化出一個AVCodecContext
8、avcodec_parameters_to_context(avCodecContext, avCodecParameters)
往編解碼器中設置流的參數
9、avcodec_open2(avCodecContext, avCodec, 0)
打開編解碼器,此時這個流的編解碼器和編解碼器上下文才可以真正使用了。
第二步 、 音視頻解碼
音視頻解碼的邏輯是每個對應的流中(音頻流 或者視頻流)定義兩個隊列(未解碼的AVPacket 和 已經解碼的AVFrame)
1、av_packet_alloc()
初始化一個AVPacket,用來存放一個從編解碼器上下文中讀出來的一幀幀數據
2、av_read_frame(avFormatContext, avPacket)
從編解碼器上下文中讀一幀數據到AVPacket
3、packages.push(avPacket)
加入到隊列
4、packages.pop(avPacket)
從隊列中取出一個數據
5、AVFrame *avFrame = av_frame_alloc()
初始化一個AVFrame結構體
6、avcodec_receive_frame(avCodecContext, avFrame)
通過編解碼器上下文中解碼出一幀數據放到avFrame結構體中
7、frames.push(avFrame);
解碼完成以後放入隊列
其中123步在一個線程中執行,4567步在另一個線程中執行
第三步 視頻的渲染
1、sws_getContext()
初始化一個SwsContext結構體,參數解釋 第123個參數分別是被轉換源的寬高和格式第456個參數轉換後的寬高和格式 第7個參數是轉換所使用的算法 剩下三個參數全部設置成NULL
2、av_image_alloc(dst_data, dst_linesize, pContext->width, pContext->height, AV_PIX_FMT_ARGB, 1)
給圖像申請內存,dst_data代表圖像通道、dst_linesize代表圖像每個通道的內存對齊的步長,即一行的對齊內存的寬度。
3、frames.pop(frame)
從已解碼的隊列中取出一幀圖像
4、sws_scale(sws_ctx, frame->data, frame->linesize, 0, pContext->height, dst_data, dst_linesize)
講解碼的原始數據轉換成yuv數據
5、渲染到屏幕上
基於SurfaceView+ANativeWindow 渲染到屏幕
https://blog.csdn.net/byhook/article/details/84027283
1、ANativeWindow_fromSurface(env, surface)
獲取到ANativeWindow 結構體
2、ANativeWindow_setBuffersGeometry(nativeWindow, width, height , WINDOW_FORMAT_RGBA_8888);
設置窗口屬性
3、ANativeWindow_lock(nativeWindow, &windowBuffer, 0)
讀取到窗口buffer數據
4、修改數據
// 填數據到buffer,其實就是修改數據
uint8_t * dst_data = static_cast<uint8_t *>(windowBuffer.bits);
int lineSize = windowBuffer.stride * 4; // RGBA
// 下面就是逐行Copy了
for (int i = 0; i < windowBuffer.height; ++i) {
// 一行Copy
memcpy(dst_data + i * lineSize, src_data + i * src_liinesize, lineSize);
}
第四步 音頻的渲染
1、創建引擎並獲取引擎接口
1、slCreateEngine(&engineObject, 0, NULL, 0, NULL, NULL);
2、(*engineObject) ->Realize(engineObject, SL_BOOLEAN_FALSE)
3、(*engineObject) ->GetInterface(engineObject, SL_IID_ENGINE, &engineInterface)
2、創建混音器
1、(*engineInterface)->CreateOutputMix(engineInterface, &outputMixObject, 0, 0, 0);
2、(*outputMixObject)->Realize(outputMixObject, SL_BOOLEAN_FALSE)
3、(*outputMixObject)->GetInterface(outputMixObject,SL_IID_ENVIRONMENTALREVERB,&outputMixEnvironmentalReverb)
4、設置
const SLEnvironmentalReverbSettings settings = SL_I3DL2_ENVIRONMENT_PRESET_DEFAULT;
(*outputMixEnvironmentalReverb)->SetEnvironmentalReverbProperties(outputMixEnvironmentalReverb, &settings);
3、創建播放器
1、配置輸入聲音信息
// 創建buffer緩衝類型的隊列 2個隊列
SLDataLocator_AndroidSimpleBufferQueue loc_bufq = {SL_DATALOCATOR_ANDROIDSIMPLEBUFFERQUEUE,
2};
// pcm數據格式
// SL_DATAFORMAT_PCM:數據格式爲pcm格式
// 2:雙聲道
// SL_SAMPLINGRATE_44_1:採樣率爲44100(44.1赫茲 應用最廣的,兼容性最好的)
// SL_PCMSAMPLEFORMAT_FIXED_16:採樣格式爲16bit (16位)(2個字節)
// SL_PCMSAMPLEFORMAT_FIXED_16:數據大小爲16bit (16位)(2個字節)
// SL_SPEAKER_FRONT_LEFT | SL_SPEAKER_FRONT_RIGHT:左右聲道(雙聲道) (雙聲道 立體聲的效果)
// SL_BYTEORDER_LITTLEENDIAN:小端模式
SLDataFormat_PCM format_pcm = {SL_DATAFORMAT_PCM, 2, SL_SAMPLINGRATE_44_1,
SL_PCMSAMPLEFORMAT_FIXED_16,
SL_PCMSAMPLEFORMAT_FIXED_16,
SL_SPEAKER_FRONT_LEFT | SL_SPEAKER_FRONT_RIGHT,
SL_BYTEORDER_LITTLEENDIAN};
// 數據源 將上述配置信息放到這個數據源中
SLDataSource audioSrc = {&loc_bufq, &format_pcm};
2、配置音軌(輸出
// 設置混音器
SLDataLocator_OutputMix loc_outmix = {SL_DATALOCATOR_OUTPUTMIX, outputMixObject};
SLDataSink audioSnk = {&loc_outmix, NULL};
// 需要的接口 操作隊列的接口
const SLInterfaceID ids[1] = {SL_IID_BUFFERQUEUE};
const SLboolean req[1] = {SL_BOOLEAN_TRUE};
3、創建播放器 CreateAudioPlayer(engineInterface, &bqPlayerObject, &audioSrc, &audioSnk, 1, ids, req);
4、初始化播放器 (*bqPlayerObject)->Realize(bqPlayerObject, SL_BOOLEAN_FALSE)
5、獲取播放器接口 (*bqPlayerObject)->GetInterface(bqPlayerObject, SL_IID_PLAY, &bqPlayerPlay)
6、設置播放回調函數
// 獲取播放器隊列接口:SLAndroidSimpleBufferQueueItf bqPlayerBufferQueue
(*bqPlayerObject)->GetInterface(bqPlayerObject, SL_IID_BUFFERQUEUE, &bqPlayerBufferQueue);
// 設置回調
void bqPlayerCallback(SLAndroidSimpleBufferQueueItf bq, void *context)
(*bqPlayerBufferQueue)->RegisterCallback(bqPlayerBufferQueue, bqPlayerCallback, this);
7、設置播放器狀態爲播放狀態 並手動激活回調函數
// 設置播放器狀態爲播放狀態
(*bqPlayerPlay)->SetPlayState(bqPlayerPlay, SL_PLAYSTATE_PLAYING);
// 手動激活回調函數
bqPlayerCallback(bqPlayerBufferQueue, this);
8、回調函數編寫(將pcm原始數據重採樣)
1、定義分配重採樣上下文
out_channels = av_get_channel_layout_nb_channels(AV_CH_LAYOUT_STEREO); // 通道數 AV_CH_LAYOUT_STEREO(雙聲道的意思)
out_sample_size = av_get_bytes_per_sample(AV_SAMPLE_FMT_S16);
out_sample_rate = 44100; // 採樣率
out_buffers_size = out_sample_rate * out_sample_size * out_channels;
out_buffers = static_cast<uint8_t *>(malloc(out_buffers_size));
memset(out_buffers, 0, out_buffers_size);
// swr_ctx = swr_alloc();
swr_ctx = swr_alloc_set_opts(0, AV_CH_LAYOUT_STEREO, AV_SAMPLE_FMT_S16, out_sample_rate,
pContext->channel_layout, pContext->sample_fmt, pContext->sample_rate, 0 ,0);
swr_init(swr_ctx);
2、計算PCM數據大小
int AudioChannel::getPCM() {
int pcm_data_size = 0;
// PCM 在 frames 隊列中
AVFrame * frame = 0;
while(isPlaying) {
int ret = frames.pop(frame);
// 如果停止播放,跳出循環, 出了循環,就要釋放
if (!isPlaying) {
break;
}
if (!ret) {
continue;
}
// PCM 的處理邏輯
frame->data;
// 音頻播放器的數據格式是我們在下面定義的(16位 雙聲道 ....)
// 而原始數據(是待播放的音頻PCM數據)
// 所以,上面的兩句話,無法統一,一個是(自己定義的16位 雙聲道 ..) 一個是原始數據,爲了解決上面的問題,就需要重採樣。
// 開始重採樣
int dst_nb_samples = av_rescale_rnd(swr_get_delay(swr_ctx, frame->sample_rate) +
frame->nb_samples, out_sample_rate, frame->sample_rate, AV_ROUND_UP);
ret = swr_convert(swr_ctx, &out_buffers, dst_nb_samples, (const uint8_t **) frame->data, frame->nb_samples);
if (ret < 0) {
fprintf(stderr, "Error while converting\n");
}
// pcm_data_size = ret * out_sample_size; // 每個聲道的數據
pcm_data_size = ret * out_sample_size * out_channels;
break;
} // end while
releaseAVFrame(&frame); // 渲染完了,frame沒用了,釋放掉
return pcm_data_size;
}
3、播放數據(*bq)->Enqueue(bq, audioChannel->out_buffers , pcmSize);