FFmpeg再学习 -- FFmpeg解码知识
繼續看雷霄驊的 課程資料 - 基于FFmpeg+SDL的視頻播放器的制作
前面用了五個篇幅來講 FFmpeg,其主要目的是為實現將圖片轉視頻的功能。
總的來說,對于 FFmepg 多少有一些了解了。但是源碼部分還是一點都不清楚。接下來簡單的梳理一下 FFmpeg 源碼結構。畢竟現在從事的工作,不太偏重這個。等以后有機會再系統的研究吧。
ffmpeg再學習 -- Linux 安裝說明
ffmpeg再學習 -- Windows下安裝說明
ffmpeg再學習 -- 將 jpeg 轉成 mp4
ffmpeg再學習 -- 硬件加速編解碼
ffmpeg再學習 -- 視音頻基礎知識
一、FFmpeg 簡介
參看:ffmpeg 源碼 來來來,README。 FFmpeg是庫和工具的集合來處理多媒體內容,如音頻、視頻、字幕和相關的元數據。其包含的庫有:
libavcodec ? ? ? ? 提供了更廣泛的編解碼器的實現。libavformat ? ? ? ?實現流協議,容器格式和基本I / O訪問。
libavutil ? ? ? ? ? ? ?包括哈希爾,解壓縮器和雜項效用函數。
libavfilter ? ? ? ? ? ?提供了通過一系列過濾器來改變已解碼的音頻和視頻的意思。
libavdevice ? ? ? ? 提供了訪問捕獲和播放設備的抽象。
libswresample ? ?實現音頻混合和重采樣程序。
libswscale ? ? ? ? ? 實現顏色轉換和縮放程序。
工具
ffmpeg ? 是用于操縱,轉換和流式傳輸多媒體內容的命令行工具箱。ffplay ? ? ?是一個簡約的多媒體播放器。
ffprobe ? 是一種檢查多媒體內容的簡單分析工具。
ffserver ? 是一種多媒體流媒體服務器,用于直播。
其他小工具,如 aviocat,ismindex 和 qt-faststart。
二、FFmpeg 解碼函數
FFmpeg解碼函數簡介
av_register_all() ? ? ? ? ? ? ? ? ? ? ? ? 注冊所有組件。avformat_open_input() ? ? ? ? ? ? 打開輸入視頻文件。
avformat_find_stream_info() ? ?獲取視頻文件信息。
avcodec_find_decoder() ? ? ? ? ? 查找解碼器。
avcodec_open2() ? ? ? ? ? ? ? ? ? ? ?打開解碼器。
av_read_frame() ? ? ? ? ? ? ? ? ? ? ? 從輸入文件讀取一幀壓縮數據。
avcodec_decode_video2() ? ? ? 解碼一幀壓縮數據。
avcodec_close() ? ? ? ? ? ? ? ? ? ? ? 關閉解碼器。
avformat_close_input() ? ? ? ? ? ?關閉輸入視頻文件。
FFmpeg解碼的流程圖如下所示
源碼解析
【架構圖】
FFmpeg源代碼結構圖 - 解碼
FFmpeg源代碼結構圖 - 編碼
【通用】
FFmpeg 源代碼簡單分析:av_register_all()
FFmpeg 源代碼簡單分析:avcodec_register_all()
FFmpeg 源代碼簡單分析:內存的分配和釋放(av_malloc()、av_free()等)
FFmpeg 源代碼簡單分析:常見結構體的初始化和銷毀(AVFormatContext,AVFrame等)
FFmpeg 源代碼簡單分析:avio_open2()
FFmpeg 源代碼簡單分析:av_find_decoder()和av_find_encoder()
FFmpeg 源代碼簡單分析:avcodec_open2()
FFmpeg 源代碼簡單分析:avcodec_close()
圖解FFMPEG打開媒體的函數avformat_open_input
FFmpeg 源代碼簡單分析:avformat_open_input()
FFmpeg 源代碼簡單分析:avformat_find_stream_info()
FFmpeg 源代碼簡單分析:av_read_frame()
FFmpeg 源代碼簡單分析:avcodec_decode_video2()
FFmpeg 源代碼簡單分析:avformat_close_input()
三、FFFmpeg解碼的數據結構
可通過,轉到定義(F12),來查看下面的結構體定義.FFmpeg解碼的數據結構如下所示
FFmpeg數據結構簡介
AVFormatContext? ? 封裝格式上下文結構體,也是統領全局的結構體,保存了視頻文件封裝格式相關信息。 iformat:輸入視頻的AVInputFormatnb_streams :輸入視頻的AVStream 個數streams :輸入視頻的AVStream []數組duration :輸入視頻的時長(以微秒為單位)bit_rate :輸入視頻的碼率 AVInputFormat
? ? 每種封裝格式(例如FLV, MKV, MP4, AVI)對應一個該結構體。 name:封裝格式名稱long_name:封裝格式的長名稱extensions:封裝格式的擴展名id:封裝格式ID 一些封裝格式處理的接口函數 AVStream
? ? 視頻文件中每個視頻(音頻)流對應一個該結構體。 id:序號codec:該流對應的AVCodecContexttime_base:該流的時基r_frame_rate: 該流的幀率 AVCodecContext
? ? 編碼器上下文結構體,保存了視頻(音頻)編解碼相關信息。 codec:編解碼器的AVCodecwidth, height:圖像的寬高(只針對視頻)pix_fmt:像素格式(只針對視頻)sample_rate:采樣率( 只針對音頻)channels:聲道數(只針對音頻)sample_fmt:采樣格式(只針對音頻) AVCodec
? ? 每種視頻(音頻)編解碼器(例如H.264解碼器)對應一個該結構體。 name:編解碼器名稱long_name:編解碼器長名稱type:編解碼器類型id:編解碼器ID一些編解碼的接口函數 AVPacket
? ? 存儲一幀壓縮編碼數據。 pts:顯示時間戳dts :解碼時間戳data :壓縮編碼數據size :壓縮編碼數據大小stream_index :所屬的AVStream AVFrame
? ? 存儲一幀解碼后像素(采樣)數據。 data:解碼后的圖像像素數據(音頻采樣數據)。linesize:對視頻來說是圖像中一行像素的大小;對音頻來說是整個音頻幀的大小。width, height:圖像的寬高(只針對視頻)。key_frame:是否為關鍵幀(只針對視頻) 。pict_type:幀類型(只針對視頻) 。例如I, P, B。
四、解碼示例
(1)源代碼如下:
/** * 最簡單的基于FFmpeg的解碼器 * Simplest FFmpeg Decoder * * 雷霄驊 Lei Xiaohua * leixiaohua1020@126.com * 中國傳媒大學/數字電視技術 * Communication University of China / Digital TV Technology * http://blog.csdn.net/leixiaohua1020 * * 本程序實現了視頻文件的解碼(支持HEVC,H.264,MPEG2等)。 * 是最簡單的FFmpeg視頻解碼方面的教程。 * 通過學習本例子可以了解FFmpeg的解碼流程。 * This software is a simplest video decoder based on FFmpeg. * Suitable for beginner of FFmpeg. * */#include <stdio.h> #include "stdafx.h"#define __STDC_CONSTANT_MACROS#ifdef _WIN32 //Windows extern "C" { #include "libavcodec/avcodec.h" #include "libavformat/avformat.h" #include "libswscale/swscale.h" }; #else //Linux... #ifdef __cplusplus extern "C" { #endif #include <libavcodec/avcodec.h> #include <libavformat/avformat.h> #include <libswscale/swscale.h> #ifdef __cplusplus }; #endif #endifint main(int argc, char* argv[]) {AVFormatContext *pFormatCtx;int i, videoindex;AVCodecContext *pCodecCtx;AVCodec *pCodec;AVFrame *pFrame, *pFrameYUV;uint8_t *out_buffer;AVPacket *packet;int y_size;int ret, got_picture;struct SwsContext *img_convert_ctx;//輸入文件路徑char filepath[] = "Titanic.ts";//創建兩個解碼后的輸出文件FILE *fp_yuv = fopen("output.yuv", "wb+");FILE *fp_h264 = fopen("output.h264", "wb+");av_register_all();//注冊所有組件avformat_network_init();//初始化網絡pFormatCtx = avformat_alloc_context();//初始化一個AVFormatContextif (avformat_open_input(&pFormatCtx, filepath, NULL, NULL) != 0) {//打開輸入的視頻文件printf("Couldn't open input stream.\n");return -1;}if (avformat_find_stream_info(pFormatCtx, NULL)<0) {//獲取視頻文件信息printf("Couldn't find stream information.\n");return -1;}videoindex = -1;for (i = 0; i < pFormatCtx->nb_streams; i++){if (pFormatCtx->streams[i]->codec->codec_type == AVMEDIA_TYPE_VIDEO) {videoindex = i;break;}}if (videoindex == -1) {printf("Didn't find a video stream.\n");return -1;}pCodecCtx = pFormatCtx->streams[videoindex]->codec;pCodec = avcodec_find_decoder(pCodecCtx->codec_id);//查找解碼器if (pCodec == NULL) {printf("Codec not found.\n");return -1;}if (avcodec_open2(pCodecCtx, pCodec, NULL)<0) {//打開解碼器printf("Could not open codec.\n");return -1;}/** 在此處添加輸出視頻信息的代碼* 取自于pFormatCtx,使用fprintf()*/FILE *fp = fopen("info.txt", "wb+");fprintf(fp,"時長:%d\n",pFormatCtx->duration);fprintf(fp,"封裝格式:%s\n",pFormatCtx->iformat->long_name);fprintf(fp,"寬高:%d*%d\n",pFormatCtx->streams[videoindex]->codec->width, pFormatCtx->streams[videoindex]->codec->height);pFrame = av_frame_alloc();pFrameYUV = av_frame_alloc();out_buffer = (uint8_t *)av_malloc(avpicture_get_size(PIX_FMT_YUV420P, pCodecCtx->width, pCodecCtx->height));avpicture_fill((AVPicture *)pFrameYUV, out_buffer, PIX_FMT_YUV420P, pCodecCtx->width, pCodecCtx->height);packet = (AVPacket *)av_malloc(sizeof(AVPacket));//Output Info-----------------------------printf("--------------- File Information ----------------\n");av_dump_format(pFormatCtx, 0, filepath, 0);printf("-------------------------------------------------\n");img_convert_ctx = sws_getContext(pCodecCtx->width, pCodecCtx->height, pCodecCtx->pix_fmt,pCodecCtx->width, pCodecCtx->height, PIX_FMT_YUV420P, SWS_BICUBIC, NULL, NULL, NULL);int frame_cnt=0;while (av_read_frame(pFormatCtx, packet) >= 0) {//讀取一幀壓縮數據if (packet->stream_index == videoindex) {/** 在此處添加輸出H264碼流的代碼* 取自于packet,使用fwrite()*/fwrite(packet->data, 1, packet->size, fp_h264); //把H264數據寫入fp_h264文件ret = avcodec_decode_video2(pCodecCtx, pFrame, &got_picture, packet);//解碼一幀壓縮數據if (ret < 0) {printf("Decode Error.\n");return -1;}if (got_picture) {sws_scale(img_convert_ctx, (const uint8_t* const*)pFrame->data, pFrame->linesize, 0, pCodecCtx->height,pFrameYUV->data, pFrameYUV->linesize);y_size = pCodecCtx->width*pCodecCtx->height;/** 在此處添加輸出YUV的代碼* 取自于pFrameYUV,使用fwrite()*/printf("Decoded frame index: %d\n", frame_cnt);fwrite(pFrameYUV->data[0], 1, y_size, fp_yuv); //Y fwrite(pFrameYUV->data[1], 1, y_size / 4, fp_yuv); //Ufwrite(pFrameYUV->data[2], 1, y_size / 4, fp_yuv); //Vframe_cnt++;}}av_free_packet(packet);}//flush decoder//FIX: Flush Frames remained in Codecint frame_cnt1 = 0;while (1) {ret = avcodec_decode_video2(pCodecCtx, pFrame, &got_picture, packet);if (ret < 0)break;if (!got_picture)break;sws_scale(img_convert_ctx, (const uint8_t* const*)pFrame->data, pFrame->linesize, 0, pCodecCtx->height,pFrameYUV->data, pFrameYUV->linesize);int y_size = pCodecCtx->width*pCodecCtx->height;printf("Flush Decoder: %d\n", frame_cnt1);fwrite(pFrameYUV->data[0], 1, y_size, fp_yuv); //Y fwrite(pFrameYUV->data[1], 1, y_size / 4, fp_yuv); //Ufwrite(pFrameYUV->data[2], 1, y_size / 4, fp_yuv); //Vframe_cnt1++;}sws_freeContext(img_convert_ctx);//關閉文件以及釋放內存fclose(fp_yuv);fclose(fp_h264);av_frame_free(&pFrameYUV);av_frame_free(&pFrame);avcodec_close(pCodecCtx);avformat_close_input(&pFormatCtx);return 0; }(2)項目下載
下載:FFmpeg 解碼 項目(3)項目說明
使用 MediaInfo 軟件查看?Titanic.ts 視頻信息
查看調式生成 info.txt 可看出對應的視頻信息
通過 YUV播放器 和?CyberLink PowerDVD 14 查看生成的視頻。
五、后續總結
(1)flush_decoder作用
當av_read_frame()循環退出的時候,實際上解碼器中可能還包含剩余的幾幀數據。因此需要通過“flush_decoder”將這幾幀數據輸出?!癴lush_decoder”功能簡而言之即直接調用avcodec_decode_video2()獲得AVFrame,而不再向解碼器傳遞AVPacket。 具體原因: 參看: avcodec_decode_video2()解碼視頻后丟幀的問題解決 打斷點也可以看到,確實有幾幀是之前沒有打印出來的。(2)解碼后的數據為什么要經過sws_scale()函數處理?
解碼后 YUV 格式的視頻像素數據保存在 AVFrame 的 data[0]、 data[1]、data[2] 中。 但是這些像素值并不是連續存儲的, 每行有效像素之后存儲了一些無效像素 。以亮度 Y 數據為例,data[0] 中一共包含了 linesize[0]*height 個數據。 但是出于優化等方面的考慮, linesize[0 ]實際上并不等于寬度 width, 而是一個比寬度大一些的值。 因此需要使用 sws_scale() 進行轉換。 轉換后去除了無效數據, width 和 linesize[0] 取值相等。(3)源碼示例
源碼中也是有示例的,查看 FFmpeg/doc/examples/
查看?decode_video.c 源碼:
/** Copyright (c) 2001 Fabrice Bellard** Permission is hereby granted, free of charge, to any person obtaining a copy* of this software and associated documentation files (the "Software"), to deal* in the Software without restriction, including without limitation the rights* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell* copies of the Software, and to permit persons to whom the Software is* furnished to do so, subject to the following conditions:** The above copyright notice and this permission notice shall be included in* all copies or substantial portions of the Software.** THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN* THE SOFTWARE.*//*** @file* video decoding with libavcodec API example** @example decode_video.c*/#include <stdio.h> #include <stdlib.h> #include <string.h>#include <libavcodec/avcodec.h>#define INBUF_SIZE 4096static void pgm_save(unsigned char *buf, int wrap, int xsize, int ysize,char *filename) {FILE *f;int i;f = fopen(filename,"w");fprintf(f, "P5\n%d %d\n%d\n", xsize, ysize, 255);for (i = 0; i < ysize; i++)fwrite(buf + i * wrap, 1, xsize, f);fclose(f); }static int decode_write_frame(const char *outfilename, AVCodecContext *avctx,AVFrame *frame, int *frame_count, AVPacket *pkt, int last) {int len, got_frame;char buf[1024];len = avcodec_decode_video2(avctx, frame, &got_frame, pkt);if (len < 0) {fprintf(stderr, "Error while decoding frame %d\n", *frame_count);return len;}if (got_frame) {printf("Saving %sframe %3d\n", last ? "last " : "", *frame_count);fflush(stdout);/* the picture is allocated by the decoder, no need to free it */snprintf(buf, sizeof(buf), "%s-%d", outfilename, *frame_count);pgm_save(frame->data[0], frame->linesize[0],frame->width, frame->height, buf);(*frame_count)++;}if (pkt->data) {pkt->size -= len;pkt->data += len;}return 0; }int main(int argc, char **argv) {const char *filename, *outfilename;const AVCodec *codec;AVCodecContext *c= NULL;int frame_count;FILE *f;AVFrame *frame;uint8_t inbuf[INBUF_SIZE + AV_INPUT_BUFFER_PADDING_SIZE];AVPacket avpkt;if (argc <= 2) {fprintf(stderr, "Usage: %s <input file> <output file>\n", argv[0]);exit(0);}filename = argv[1];outfilename = argv[2];avcodec_register_all();av_init_packet(&avpkt);/* set end of buffer to 0 (this ensures that no overreading happens for damaged MPEG streams) */memset(inbuf + INBUF_SIZE, 0, AV_INPUT_BUFFER_PADDING_SIZE);/* find the MPEG-1 video decoder */codec = avcodec_find_decoder(AV_CODEC_ID_MPEG1VIDEO);if (!codec) {fprintf(stderr, "Codec not found\n");exit(1);}c = avcodec_alloc_context3(codec);if (!c) {fprintf(stderr, "Could not allocate video codec context\n");exit(1);}if (codec->capabilities & AV_CODEC_CAP_TRUNCATED)c->flags |= AV_CODEC_FLAG_TRUNCATED; // we do not send complete frames/* For some codecs, such as msmpeg4 and mpeg4, width and heightMUST be initialized there because this information is notavailable in the bitstream. *//* open it */if (avcodec_open2(c, codec, NULL) < 0) {fprintf(stderr, "Could not open codec\n");exit(1);}f = fopen(filename, "rb");if (!f) {fprintf(stderr, "Could not open %s\n", filename);exit(1);}frame = av_frame_alloc();if (!frame) {fprintf(stderr, "Could not allocate video frame\n");exit(1);}frame_count = 0;for (;;) {avpkt.size = fread(inbuf, 1, INBUF_SIZE, f);if (avpkt.size == 0)break;/* NOTE1: some codecs are stream based (mpegvideo, mpegaudio)and this is the only method to use them because you cannotknow the compressed data size before analysing it.BUT some other codecs (msmpeg4, mpeg4) are inherently framebased, so you must call them with all the data for oneframe exactly. You must also initialize 'width' and'height' before initializing them. *//* NOTE2: some codecs allow the raw parameters (frame size,sample rate) to be changed at any frame. We handle this, soyou should also take care of it *//* here, we use a stream based decoder (mpeg1video), so wefeed decoder and see if it could decode a frame */avpkt.data = inbuf;while (avpkt.size > 0)if (decode_write_frame(outfilename, c, frame, &frame_count, &avpkt, 0) < 0)exit(1);}/* Some codecs, such as MPEG, transmit the I- and P-frame with alatency of one frame. You must do the following to have achance to get the last frame of the video. */avpkt.data = NULL;avpkt.size = 0;decode_write_frame(outfilename, c, frame, &frame_count, &avpkt, 1);fclose(f);avcodec_free_context(&c);av_frame_free(&frame);return 0; }編譯:
make 編譯會出現如下錯誤: Package libavdevice was not found in the pkg-config search path. Perhaps you should add the directory containing `libavdevice.pc' to the PKG_CONFIG_PATH environment variable No package 'libavdevice' found 查看 README,有一種方法: Method 1: build the installed examples in a generic read/write user directoryCopy to a read/write user directory and just use "make", it will link to the libraries on your system, assuming the PKG_CONFIG_PATH is correctly configured. 意思就是配置?PKG_CONFIG_PATH 庫位置 參看:從編譯ffmpeg/examples,緊接著了解pkg-config?解決方法: 在 /etc/profile 最后添加: export PKG_CONFIG_PATH=/usr/local/ffmpeg/lib/pkgconfig:$PKG_CONFIG_PATH 執行: source /etc/profile 立即生效然后再 make 編譯,生成文件:
總結
以上是生活随笔為你收集整理的FFmpeg再学习 -- FFmpeg解码知识的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 2022蓝骑士发展与保障报告
- 下一篇: DNS服务器systemctl star