一. FFmpeg 音視頻解碼流程
平常我們播放媒體文件時(shí)稽穆,通常需要經(jīng)過以下幾個(gè)步驟:二. 解協(xié)議
將流媒體協(xié)議的數(shù)據(jù),解析為標(biāo)準(zhǔn)的相應(yīng)的封裝格式數(shù)據(jù)穆咐。視音頻在網(wǎng)絡(luò)上傳播的時(shí)候方篮,常常采用各種流媒體協(xié)議晾剖,例如 HTTP,RTMP侍郭,或是 MMS 等等询吴。這些協(xié)議在傳輸視音頻數(shù)據(jù)的同時(shí)俩垃,也會傳輸一些信令數(shù)據(jù)。這些信令數(shù)據(jù)包括對播放的控制(播放汰寓,暫停口柳,停止),或者對網(wǎng)絡(luò)狀態(tài)的描述等有滑。解協(xié)議的過程中會去除掉信令數(shù)據(jù)而只保留視音頻數(shù)據(jù)跃闹。例如,采用 RTMP 協(xié)議傳輸?shù)臄?shù)據(jù)毛好,經(jīng)過解協(xié)議操作后望艺,輸出 FLV 格式的數(shù)據(jù)。
三. 解封裝
將輸入的封裝格式的數(shù)據(jù)肌访,分離成為音頻流壓縮編碼數(shù)據(jù)和視頻流壓縮編碼數(shù)據(jù)找默。封裝格式種類很多,例如 MP4吼驶,MKV惩激,RMVB,TS蟹演,F(xiàn)LV风钻,AVI 等等,它的作用就是將已經(jīng)壓縮編碼的視頻數(shù)據(jù)和音頻數(shù)據(jù)按照一定的格式放到一起酒请。例如骡技,F(xiàn)LV 格式的數(shù)據(jù),經(jīng)過解封裝操作后羞反,輸出 H.264 編碼的視頻碼流和 AAC 編碼的音頻碼流布朦。
四. 解碼
將視頻/音頻壓縮編碼數(shù)據(jù),解碼成為非壓縮的視頻/音頻原始數(shù)據(jù)昼窗。解碼是整個(gè)系統(tǒng)中最重要也是最復(fù)雜的一個(gè)環(huán)節(jié)是趴。通過解碼,壓縮編碼的視頻數(shù)據(jù)輸出成為非壓縮的顏色數(shù)據(jù)膏秫,例如 YUV420P右遭,RGB 等等;
五. 音視頻同步
根據(jù)解封裝模塊處理過程中獲取到的參數(shù)信息缤削,同步解碼出來的視頻和音頻數(shù)據(jù)窘哈,并將視頻音頻數(shù)據(jù)送至系統(tǒng)的顯卡和聲卡播放出來。
六. FFmpeg接口使用
1.在使用FFmpeg解碼媒體文件之前亭敢,首先需要注冊了容器和編解碼器有關(guān)的組件
av_register_all()
如果我們需要播放網(wǎng)絡(luò)多媒體滚婉,則可以加載socket庫以及網(wǎng)絡(luò)加密協(xié)議相關(guān)的庫,為后續(xù)使用網(wǎng)絡(luò)相關(guān)提供支持帅刀。
avformat_network_init();
2.我們通過avformat_open_input()來打開一個(gè)媒體文件让腹,并獲得媒體文件封裝格式的上下文
//打開一個(gè)文件并解析远剩。可解析的內(nèi)容包括:視頻流骇窍、音頻流瓜晤、視頻流參數(shù)、音頻流參數(shù)腹纳、視頻幀索引
int res = avformat_open_input(&pAVFormatCtx, url, NULL, NULL);
LOGI("avformat_open_input %s %d", url, res);
if(res != 0){
LOGE("can not open url :%s", url);
callJava->onCallError(CHILD_THREAD, 1001, "can not open url");
exit = true;
pthread_mutex_unlock(&init_mutex);
return;
}
3.通過avformat_find_stream_info()獲取媒體文件中痢掠,提取流的上下文信息,分離出音視頻流嘲恍。
//解碼時(shí)足画,作用是從文件中提取流信,將所有的Stream的MetaData信息填充好佃牛,先read_packet一段數(shù)據(jù)解碼分析流數(shù)據(jù)
if(avformat_find_stream_info(pAVFormatCtx, NULL) < 0){
LOGE("can not find streams from %s", url);
callJava->onCallError(CHILD_THREAD, 1002,"can not find streams from url");
exit = true;
pthread_mutex_unlock(&init_mutex);
return;
}
通過遍歷找出文件中的音頻流或視頻流
for(int i = 0; i < pAVFormatCtx->nb_streams; i++){
if(pAVFormatCtx->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_AUDIO){
//得到音頻流
if(audio == NULL){
audio = new FFAudio(playstatus, pAVFormatCtx->streams[i]->codecpar->sample_rate, callJava);
audio->streamIndex = i;
audio->codecpar = pAVFormatCtx->streams[i]->codecpar;
audio->duration = pAVFormatCtx->duration / AV_TIME_BASE;
audio->time_base = pAVFormatCtx->streams[i]->time_base;
duration = audio->duration;
//av_q2d(time_base)=每個(gè)刻度是多少秒
LOGI("audio stream_info[%d], duration:%d, time_base den:%d, sample_rate:%d",
i, audio->duration, audio->time_base.den, pAVFormatCtx->streams[i]->codecpar->sample_rate);
LOGI("audio stream_info[%d], duration %lld", i, pAVFormatCtx->duration);
}
} else if (pAVFormatCtx->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO){
//得到視頻流
if (video == NULL){
video = new FFVideo(playstatus, callJava);
video->streamIndex = i;
video->codecpar = pAVFormatCtx->streams[i]->codecpar;
video->time_base = pAVFormatCtx->streams[i]->time_base;
int num = pAVFormatCtx->streams[i]->avg_frame_rate.num;
int den = pAVFormatCtx->streams[i]->avg_frame_rate.den;
LOGI("video stream_info[%d], frame_rate num %d,den %d", i, num, den);
if(num != 0 && den != 0){
int fps = num / den;//[25 / 1]
video->defaultDelayTime = 1.0 / fps;
}
LOGI("video stream_info[%d], defaultDelayTime is %f", i, video->defaultDelayTime);
}
}
}
4.在使用FFmpeg解碼媒體文件之前淹辞,首先需要注冊了容器和編解碼器有關(guān)的組件
//查找對應(yīng)的解碼器 存儲編解碼器信息的結(jié)構(gòu)體
AVCodec *avCodec = avcodec_find_decoder(codecpar->codec_id);// 軟解
//avCodec = avcodec_find_decoder_by_name("mp3_mediacodec"); // 硬解
if (!avCodec){
LOGE("MFFmpeg::getCodecContext can not find decoder!");
callJava->onCallError(CHILD_THREAD, 1003, "can not find decoder");
exit = true;
pthread_mutex_unlock(&init_mutex);
return -1;
}
LOGI("getCodecContext codecpar-> 解碼類型:%d 編碼格式:%s" , codecpar->codec_type, avCodec->name);
//配置解碼器
*avCodecContext = avcodec_alloc_context3(avCodec);
if (!*avCodecContext){
LOGE("can not alloc new decodecctx");
callJava->onCallError(CHILD_THREAD, 1004, "can not alloc new decodecctx");
exit = true;
pthread_mutex_unlock(&init_mutex);
return -1;
}
5.通過avcodec_open2()打開解碼器,解碼媒體文件俘侠。
//打開編解碼器
if(avcodec_open2(*avCodecContext, avCodec, 0) != 0){
LOGE("cant not open strames");
callJava->onCallError(CHILD_THREAD, 1006, "cant not open strames");
exit = true;
pthread_mutex_unlock(&init_mutex);
return -1;
}
6.打開解碼器之后象缀,通過av_read_frame()一幀一幀讀取壓縮數(shù)據(jù)。
AVPacket \*avPacket = av\_packet\_alloc();
//讀取具體的音/視頻幀數(shù)據(jù)
int ret = av_read_frame(pAVFormatCtx, avPacket);
if (ret==0){
//stream_index:標(biāo)識該AVPacket所屬的視頻/音頻流
if(avPacket->stream_index == audio->streamIndex){
//LOGI("audio 解碼第 %d 幀 DTS:%lld PTS:%lld", count, avPacket->dts, avPacket->pts);
audio->queue->putAVpacket(avPacket);
} else if(avPacket->stream_index == video->streamIndex){
//LOGI("video 解碼第 %d 幀 DTS:%lld PTS:%lld", count, avPacket->dts, avPacket->pts);
count++;
video->queue->putAVpacket(avPacket);
} else{
av_packet_free(&avPacket);
av_free(avPacket);
avPacket = NULL;
}
}
7.通過avcodec_decode_video2()/avcodec_decode_audio4解碼一幀視頻或者音壓縮數(shù)據(jù)兼贡,通過AVPacket->
AVFrame得到視頻像素?cái)?shù)據(jù)攻冷。
//解碼AVPacket->AVFrame
ret = avcodec_decode_audio4(pCodeCtx, frame, &got_frame, packet);
//解碼一幀視頻壓縮數(shù)據(jù)娃胆,得到視頻像素?cái)?shù)據(jù)
ret = avcodec_decode_video2(pCodecCtx, pFrame, &got_picture, packet);