基於ffmpeg+opencv的h264解碼顯示功能的實現
阿新 • • 發佈:2019-02-13
最近做的一個專案中需要對h264編碼的視訊檔案進行解碼並轉換為openc可以處理的格式進行顯示和相關的影象處理操作。從網上找了借鑑了很多資料,但做的相對來說比較簡單。因此,在網上現有的程式碼基礎上進行了整理和優化。目前在專案中整合,效果良好。特分享給有共同需求的朋友,如有可繼續優化的空間,還望各位朋友能指出,我們共同學習與提高。
下面,貼出相關程式碼。
decode_video.h
#ifndef _DECODE_VIDEO_H
#define _DECODE_VIDEO_H
#include "opencv2/opencv.hpp"
extern "C"
{
#include "libavcodec/avcodec.h"
#include "libavformat/avformat.h"
#include "libswscale/swscale.h"
};
typedef struct video_info
{
AVPacket *packet;
AVFrame *pAvFrame;
AVCodec *pCodec;
AVFormatContext *pFormatCtx;
AVCodecContext *pCodecCtx;
SwsContext *img_convert_ctx;
int videoindex;
}video_t;
#ifndef G_DECODE_VIDEO_H
#define G_DECODE_VIDEO_H extern
#endif
G_DECODE_VIDEO_H video_t* video_init(const char* video_filename,int*ret);
G_DECODE_VIDEO_H int video_get_frame(video_t* handel,cv::Mat* pCvMat);
G_DECODE_VIDEO_H int video_get_alltime(video_t* handel);
G_DECODE_VIDEO_H int video_seek_frame(video_t* handel,long time_start);
G_DECODE_VIDEO_H int video_uninit(video_t* handel);
#endif
decode_video.cpp
#include "decode_video.h"
G_DECODE_VIDEO_H video_t* video_init(const char* video_filename,int *ret)
{
video_t* video_info = (video_t*)malloc(sizeof(video_t));
video_info->packet = NULL;
video_info->pAvFrame = NULL;
video_info->pCodec = NULL;
video_info->pFormatCtx = NULL;
video_info->pCodecCtx = NULL;
video_info->img_convert_ctx = NULL;
video_info->videoindex = -1;
av_register_all();
if (avformat_open_input(&(video_info->pFormatCtx),video_filename, NULL, NULL) != 0)
{
//無法開啟檔案
(*ret) = -1;
return NULL;
}
if (avformat_find_stream_info(video_info->pFormatCtx,NULL) < 0)
{
//無法查詢到流資訊
(*ret) = -2;
return NULL;
}
for(int i = 0;i < video_info->pFormatCtx->nb_streams;i++)
{
if (video_info->pFormatCtx->streams[i]->codec->codec_type == AVMEDIA_TYPE_VIDEO)
{
video_info->videoindex = i;
break;
}
}
if(video_info->videoindex == -1)
{
//無法找到視訊流
(*ret) = -3;
return NULL;
}
video_info->pCodecCtx = video_info->pFormatCtx->streams[video_info->videoindex]->codec;
video_info->pCodec = avcodec_find_decoder(video_info->pCodecCtx->codec_id);
if (video_info->pCodec == NULL)
{
(*ret) = -4;
return NULL;
}
if (avcodec_open2(video_info->pCodecCtx,video_info->pCodec, NULL) < 0)
{
//無法開啟解碼器
(*ret) = -5;
return NULL;
}
video_info->pAvFrame = av_frame_alloc();
int y_size = video_info->pCodecCtx->width * video_info->pCodecCtx->height;
video_info->packet = (AVPacket *)av_malloc(sizeof(AVPacket));
av_new_packet(video_info->packet, y_size);
(*ret) = 0;
return video_info;
}
void video_getimg(AVCodecContext * pCodecCtx, SwsContext * img_convert_ctx, AVFrame * pFrame,cv::Mat* pCvMat)
{
if (pCvMat->empty())
{
pCvMat->create(cv::Size(pCodecCtx->width, pCodecCtx->height), CV_8UC3);
}
AVFrame *pFrameRGB = NULL;
uint8_t *out_bufferRGB = NULL;
pFrameRGB = av_frame_alloc();
//給pFrameRGB幀加上分配的記憶體;
int size = avpicture_get_size(AV_PIX_FMT_BGR24, pCodecCtx->width, pCodecCtx->height);
out_bufferRGB = new uint8_t[size];
avpicture_fill((AVPicture *)pFrameRGB, out_bufferRGB, AV_PIX_FMT_BGR24, pCodecCtx->width, pCodecCtx->height);
//YUV to RGB
sws_scale(img_convert_ctx, pFrame->data, pFrame->linesize, 0, pCodecCtx->height, pFrameRGB->data, pFrameRGB->linesize);
memcpy(pCvMat->data, out_bufferRGB, size);
delete[] out_bufferRGB;
av_free(pFrameRGB);
}
G_DECODE_VIDEO_H int video_get_frame(video_t* handel,cv::Mat* pCvMat)
{
int result = 0;
int pic_got = -1;
result = av_read_frame(handel->pFormatCtx,handel->packet);
if(result < 0)
{
//視訊播放完成
pCvMat = NULL;
return -6;
}
//此處需注意,視訊播放完成後,並不會輸出-6,而是會再進行解碼導致解碼錯誤輸出-7
if (handel->packet->stream_index == handel->videoindex)
{
int state = avcodec_decode_video2(handel->pCodecCtx, handel->pAvFrame, &pic_got, handel->packet);
if (state < 0)
{
//解碼錯誤
pCvMat = NULL;
return -7;
}
if (pic_got)
{
if (handel->img_convert_ctx == NULL)
{
handel->img_convert_ctx = sws_getContext(handel->pCodecCtx->width, handel->pCodecCtx->height,handel->pCodecCtx->pix_fmt, handel->pCodecCtx->width, handel->pCodecCtx->height,AV_PIX_FMT_BGR24, SWS_BICUBIC, NULL, NULL, NULL);
}
if (pCvMat->empty())
{
pCvMat->create(cv::Size(handel->pCodecCtx->width, handel->pCodecCtx->height), CV_8UC3);
}
if (handel->img_convert_ctx != NULL)
{
video_getimg(handel->pCodecCtx, handel->img_convert_ctx, handel->pAvFrame,pCvMat);
}
}
}
av_free_packet(handel->packet);
return 0;
}
G_DECODE_VIDEO_H int video_get_alltime(video_t* handel)
{
int hours, mins, secs, us;
if (handel->pFormatCtx->duration != AV_NOPTS_VALUE)
{
int64_t duration = handel->pFormatCtx->duration + 5000;
secs = duration / AV_TIME_BASE;
us = duration % AV_TIME_BASE;
mins = secs / 60;
secs %= 60;
hours = mins / 60;
mins %= 60;
return (hours * 3600 + mins * 60 + secs);
}
else
{
return 0;
}
}
G_DECODE_VIDEO_H int video_seek_frame(video_t* handel,long time_start)
{
int64_t seek_pos = 0;
if (time_start < 0)
{
return -1;
}
seek_pos = time_start * AV_TIME_BASE;
if (handel->pFormatCtx->start_time != AV_NOPTS_VALUE)
{
seek_pos += handel->pFormatCtx->start_time;
}
if (av_seek_frame(handel->pFormatCtx, -1, seek_pos, AVSEEK_FLAG_ANY) < 0)
{
return -2;
}
return 0;
}
G_DECODE_VIDEO_H int video_uninit(video_t* handel)
{
if(handel != NULL)
{
av_free_packet(handel->packet);
avcodec_close(handel->pCodecCtx);
avformat_close_input(&(handel->pFormatCtx));
return 0;
}
else
{
return -1;
}
}