項目源碼
解碼分爲軟解碼和硬解碼,那麼什麼是軟解碼和硬解碼,二者有什麼區別?簡單來說,在於是否使用CPU進行解碼,最初視頻解碼都是通過CPU進行的,那時候視頻分辨率較低,CPU完全可以勝任解碼的工作,但是隨着高清視頻的出現,使用CPU進行解碼的壓力越來越大
軟解碼
使用CPU進行解碼,所以就很容易造成CPU負載過大。純粹依靠CPU來解碼,是在顯卡本身不支持或者部分不支持硬件解碼的前提下,將解壓高清編碼的任務交給CPU,這是基於硬件配置本身達不到硬解壓要求的前提下的無奈之舉。我們在解碼過程中,應該在硬件支持的前提下優先使用硬解碼。
對於一個超級電視而言,觀看高清電影無疑是用戶最大的訴求,而硬解碼的優勢就在於可以流暢的支持1080p甚至4K清晰度的電影播放,而不需要佔用CPU,CPU就可以如釋重負,輕鬆上陣,承擔更多的其他任務。如果通過軟解碼的方式播放高清電影,CPU的負擔較重,往往會出現卡頓、不流暢的現象。
硬解碼
硬件解碼就是通過顯卡的視頻加速功能對高清視頻進行解碼,使用非CPU進行,如GPU/VPU(GPU:圖形處理器:Graphics Processing Unit,指計算機的顯卡,VPU:Visual Processing Unit,視覺處理單元,由ATI提出的、用於區別於傳統GPU的概念,實際二者均爲顯示處理核心,本質上並無任何區別)、專用的DSP、FPGA、ASIC芯片等,所以幾乎不會佔用CPU,部分產品在GPU硬件平臺移植了優秀的軟編碼算法(如X264),解碼質量基本等同於軟編碼。硬解碼可以將CPU從繁重的視頻解碼中解放出來,使播放設備具備流暢播放高清視頻的能力。顯卡的GPU/VPU要比CPU更適合這類大數據量的、低難度的重複工作。
那麼ffmpeg是如何進行軟解碼和硬解碼的,分別來看
解碼步驟
使用ffmpeg解碼分爲如下幾步:
1.找到解碼器
avcodec_find_decoder(軟) avcodec_find_decoder_by_name(硬)
2.獲取解碼器上下文
avcodec_alloc_context3
3.填充解碼器參數到上下文中
avcodec_parameters_to_context
4.打開解碼器
avcodec_open2
5.將解封裝得到的AVPacket發送到解碼器中進行解碼
avcodec_send_packet
6.從解碼器中接收已經解碼完成的數據存入AVFrame
avcodec_receive_frame
7.釋放frame空間
av_frame_unref
具體到代碼中看一看音視頻的軟解碼和硬解碼
視頻軟解碼環境
/***************************************video解碼器*********************************************/
//找到視頻解碼器(軟解碼)
AVCodec *videoAVCodec = avcodec_find_decoder(avFormatContext->streams[videoIndex]->codecpar->codec_id);
avcodec_open2 video failed!
if (videoAVCodec == NULL){
LOGE("avcodec_find_decoder failed !");
return;
}
//初始化視頻解碼器上下文對象
AVCodecContext *videoCodecContext = avcodec_alloc_context3(videoAVCodec);
//根據所提供的編解碼器的值填充編解碼器上下文參數
avcodec_parameters_to_context(videoCodecContext,avFormatContext->streams[videoIndex]->codecpar);
//設置視頻解碼器解碼的線程數,解碼時將會以你設定的線程進行解碼
videoCodecContext->thread_count = 8;
//打開解碼器
result = avcodec_open2(videoCodecContext,NULL,NULL);
if (result != 0){
LOGE("avcodec_open2 video failed! %s",av_err2str(result));
return;
}
/***********************************************************************************************/
視頻硬解碼環境
//硬解碼,硬解碼需要Jni_OnLoad中做設置否則ffmpeg_player_error: avcodec_open2 video failed!
AVCodec *videoAVCodec = avcodec_find_decoder_by_name("h264_mediacodec");
if (videoAVCodec == NULL){
LOGE("avcodec_find_decoder failed !");
return;
}
//初始化視頻解碼器上下文對象
AVCodecContext *videoCodecContext = avcodec_alloc_context3(videoAVCodec);
//根據所提供的編解碼器的值填充編解碼器上下文參數
avcodec_parameters_to_context(videoCodecContext,avFormatContext->streams[videoIndex]->codecpar);
//設置視頻解碼器解碼的線程數,解碼時將會以你設定的線程進行解碼
videoCodecContext->thread_count = 8;
//打開解碼器
result = avcodec_open2(videoCodecContext,NULL,NULL);
if (result != 0){
LOGE("avcodec_open2 video failed! %s",av_err2str(result));
return;
}
可以看到軟硬解碼的區別也只是在於獲取解碼器的那一步操作
音頻軟解碼環境
/***************************************audio解碼器*********************************************/
//找到音頻解碼器(軟解碼)
AVCodec *audioAVCodec = avcodec_find_decoder(avFormatContext->streams[audioIndex]->codecpar->codec_id);
//初始化音頻解碼器上下文對象
AVCodecContext *audioCodecContext = avcodec_alloc_context3(audioAVCodec);
//根據所提供的編解碼器的值填充編解碼器上下文參數
avcodec_parameters_to_context(audioCodecContext,avFormatContext->streams[audioIndex]->codecpar);
//設置音頻解碼器解碼的線程數,解碼時將會以你設定的線程進行解碼
audioCodecContext->thread_count = 1;
//打開音頻解碼器
result = avcodec_open2(audioCodecContext,NULL,NULL);
if (result != 0){
LOGE("avcodec_open2 audio failed!");
return;
}
/***********************************************************************************************/
音頻硬解碼環境
/***************************************audio解碼器*********************************************/
//找到音頻解碼器(軟解碼)
//硬解碼
AVCodec *audioAVCodec = avcodec_find_decoder_by_name("h264_mediacodec");
//初始化音頻解碼器上下文對象
AVCodecContext *audioCodecContext = avcodec_alloc_context3(audioAVCodec);
//根據所提供的編解碼器的值填充編解碼器上下文參數
avcodec_parameters_to_context(audioCodecContext,avFormatContext->streams[audioIndex]->codecpar);
//設置音頻解碼器解碼的線程數,解碼時將會以你設定的線程進行解碼
audioCodecContext->thread_count = 1;
//打開音頻解碼器
result = avcodec_open2(audioCodecContext,NULL,NULL);
if (result != 0){
LOGE("avcodec_open2 audio failed!");
return;
}
/***********************************************************************************************/
同樣也是區別在於解碼器的獲取
獲取到解碼器之後解碼的過程代碼如下:
for (;;) {
//********************測試每秒解碼幀數代碼*******************
if(GetNowMs() - start >=3000){
LOGI("now decode fps is %d",frameCount/3);
start = GetNowMs();
frameCount = 0;
}
//********************測試每秒解碼幀數代碼*******************
//Return the next frame of a stream.
int read_result = av_read_frame(avFormatContext,avPacket);
if(read_result != 0){
//讀取到結尾處,從20秒位置繼續開始播放
LOGI("讀取到結尾處 %s",av_err2str(read_result));
//跳轉到指定的position播放,最後一個參數表示
//int pos = 200000 * r2d(avFormatContext->streams[videoIndex]->time_base);
//av_seek_frame(avFormatContext,videoIndex,pos,AVSEEK_FLAG_BACKWARD|AVSEEK_FLAG_FRAME );
//LOGI("avFormatContext->streams[videoIndex]->time_base= %d",avFormatContext->streams[videoIndex]->time_base);
//continue;
break;
}
LOGW("stream = %d size =%d pts=%lld flag=%d pos = %d",
avPacket->stream_index,avPacket->size,avPacket->pts,avPacket->flags,avPacket->pos
);
//解碼測試
if(avPacket->stream_index != videoIndex){
continue;
}
AVCodecContext *codecContext = videoCodecContext;
if (avPacket->stream_index == audioIndex){
codecContext = audioCodecContext;
}
//將packet發送到解碼器中進行解碼
read_result = avcodec_send_packet(videoCodecContext,avPacket);
if (read_result != 0){
LOGE("avcodec_send_packet failed!");
continue;
}
for(;;){
//從解碼器中返回的已經解碼的數據
read_result = avcodec_receive_frame(codecContext,avFrame);
if(read_result != 0){
LOGE("avcodec_receive_frame failed!");
break;
}
//********************測試每秒解碼幀數代碼*******************
//說明解碼的是視頻,
if(codecContext == videoCodecContext) {
frameCount++;
}
//********************測試每秒解碼幀數代碼*******************
LOGW("avcodec_receive_frame %lld",avFrame->pts);
}
//packet使用完成之後執行,否則內存會急劇增長
//不再引用這個packet指向的空間,並且將packet置爲default狀態
av_packet_unref(avPacket);
}
軟硬解碼已經多線程解碼效率測試結果
neon 單線程下解碼視頻結果:
每秒幀數27~68
CPU佔用16%左右
內存佔用67M左右
neon 八線程下解碼視頻結果
每秒解碼幀數100~170幀
CPU佔用70%左右
內存佔用90M左右
neon h264_mediacodec硬解碼 設置單線程
每秒解碼幀數46~58,硬解碼是一個固定值,這是由於計算誤差產生的
CPU佔用3%左右
內存佔用23M左右
neon h264_mediacodec硬解碼 設置8線程
每秒解碼幀數46~85,線程數對幀率無影響,因爲硬解碼幀率是固定的
CPU和內存的佔用可忽略不計
總結一下
1.軟解碼相較於硬解碼,對內存和CPU的開銷都明顯很大
2.硬解碼的解碼幀率是固定的,但是幾乎不佔用CPU