摘要
這篇文章介紹使用FFmpeg實現視頻解碼,具體操作爲讀取上一節視頻解碼所生成的YUV420p格式視頻文件,對其進行H.264格式視頻編碼,並將編碼後的數據保存爲H.264裸流文件Sample.h264。
初始化FFmpeg
所有操作之前必須先註冊FFmpeg組件實現全局初始化。
void VideoEncoding::init()
{
avcodec_register_all();
}
配置編解碼器CodecContext
- 這裏要進行H.264格式的視頻編碼,所以要先手動指定編碼器名字爲
libx264
。更多可用編碼器可以使用命令ffmpeg -encoders
查看。 - 接着調用
avcodec_alloc_context3
函數申請CodecContext。 - 然後手動給CodecContext配置各種編碼參數。
- 最後調用
avcodec_open2
完成編碼器配置。
// Configure AVCodecContext parameters Manually
bool VideoEncoding::initCodecContext()
{
const AVCodec *enc = avcodec_find_encoder_by_name("libx264");
//const AVCodec *enc = avcodec_find_encoder(AV_CODEC_ID_H264);
if (!enc) {
fprintf(stderr, "Failed to find encoder\n");
return true;
}
mCodecCtx = avcodec_alloc_context3(enc);
if (!mCodecCtx) {
printf("Failed to allocate the codec context\n");
return true;
}
// 根據實際情況修改編碼器參數
// 這裏只進行編碼操作,所以分辨率應和原始文件相同
mCodecCtx->width = 1280; //與YUV文件分辨率一致。
mCodecCtx->height = 534;
mCodecCtx->bit_rate = 1000000; //碼率1Mbps
mCodecCtx->gop_size = 10;
mCodecCtx->time_base = { 1, 24 };
mCodecCtx->framerate = { 24, 1 }; //幀率
mCodecCtx->max_b_frames = 1;
mCodecCtx->pix_fmt = AV_PIX_FMT_YUV420P; //像素格式YUV420p
// 根據需要選擇編碼器預設編碼模式
if (enc->id == AV_CODEC_ID_H264) {
//此模式編碼比較慢,會先緩衝幾幀後再進行編碼
//av_opt_set(mCodecCtx->priv_data, "preset", "slow", 0); // delay ~18 frames
//這種模式編碼速度比較快,沒有延時
av_opt_set(mCodecCtx->priv_data, "tune", "zerolatency", 0); // no delay
}
// Initialize mCodecCtx to use the given Codec
if (avcodec_open2(mCodecCtx, enc, NULL) < 0) {
printf("Failed to open codec\n");
return true;
}
return false;
}
讀取視頻、視頻編碼、寫入文件
- 先申請一個buffer用來存放從YUV文件讀取到的frame數據。
- 與解碼寫文件操作相逆,按照格式把文件數據循環裝到多個frame結構中。我們使用的是YUV420p格式的數據。
- 使用
avcodec_send_frame
和avcodec_receive_packet
這一對函數進行視頻編碼,發送一個未編碼的frame,接收一個編碼後的packet。 - 最後把編碼後的packet依次寫入文件。
需要注意的是,使用不同的編碼預設參數會導致編碼的速度。也就是說avcodec_send_frame
發送一個frame到編碼器,可能使用avcodec_receive_packet
不能立即接收到一個編碼後的packet。所以在發送完所有的frame之後,循環接收編碼器返回的編碼數據,直接所有的數據接收完畢。最後寫入幾個字節的H264裸流標識。
bool VideoEncoding::readFrameProc(const char * input, const char * output)
{
FILE *yuvFd = fopen(input, "rb"); //輸入的YUV420p文件Sample.yuv。
FILE *outFd = fopen(output, "wb"); //輸出文件名Sample.h264。
if (!outFd || !yuvFd) {
fprintf(stderr, "Could not open file\n");
return true;
}
AVFrame *frame = NULL;
//申請一個AVFrame
if (!(frame = av_frame_alloc())) {
printf("Failed to allocate video frame\n");
return true;
}
//使用CodecContext設置AVFrame參數
frame->format = mCodecCtx->pix_fmt; //上一步設置的像素格式 AV_PIX_FMT_YUV420P
frame->width = mCodecCtx->width;
frame->height = mCodecCtx->height;
//爲frame申請存儲視頻的data空間
if (av_frame_get_buffer(frame, 32) < 0) {
printf("Failed to allocate the video frame data\n");
return true;
}
int num = 0, i = 0;
AVPacket pkt;
// read a frame every time
while (!feof(yuvFd)) {
av_init_packet(&pkt);
pkt.data = NULL; // packet data will be allocated by the encoder
pkt.size = 0;
if (av_frame_make_writable(frame)) {
return true;
}
// 讀取yuv420p視頻文件到frame.data
fread(frame->data[0], 1, mCodecCtx->width *mCodecCtx->height, yuvFd);
fread(frame->data[1], 1, mCodecCtx->width*mCodecCtx->height / 4, yuvFd);
fread(frame->data[2], 1, mCodecCtx->width*mCodecCtx->height / 4, yuvFd);
frame->pts = i++;
//視頻編碼:發送frame,接收packet
avcodec_send_frame(mCodecCtx, frame);
int ret = avcodec_receive_packet(mCodecCtx, &pkt);
if (!ret) {
printf("Write frame %3d (size=%5d)\n", i, pkt.size);
fwrite(pkt.data, 1, pkt.size, outFd);
av_packet_unref(&pkt);
}
printf("------------------------------------\n");
}
// 編碼有延時,獲取延時的數據
for (;; i++) {
//持續接收編碼器發送過來的數據
avcodec_send_frame(mCodecCtx, NULL);
int ret = avcodec_receive_packet(mCodecCtx, &pkt);
if (ret == 0) {
printf("Write frame %3d (size=%5d)\n", i, pkt.size);
fwrite(pkt.data, 1, pkt.size, outFd);
av_packet_unref(&pkt);
}
else if (ret == AVERROR_EOF) { //接收完畢
printf("Write frame complete\n");
break;
}
else {
printf("Error encoding frame\n");
break;
}
}
//看別人都加上這個標識了(#^.^#)
uint8_t endcode[] = { 0, 0, 1, 0xb7 };
/* add sequence end code to have a real MPEG file */
fwrite(endcode, 1, sizeof(endcode), outFd);
fclose(outFd);
fclose(yuvFd);
av_frame_free(&frame);
return false;
}
釋放系統資源
最後要釋放相關的資源。
VideoEncoding::~VideoEncoding()
{
avcodec_free_context(&mCodecCtx);
}
示例程序代碼
上述示例的完整代碼可以從Github下載: https://github.com/lmshao/FFmpeg-Basic 。