參考資料 https://zhuanlan.zhihu.com/p/142593316
ffmpeg概念
多媒體文件是一個(gè)容器,在容器中有很多流,每個(gè)流是由不同的編碼器編碼的,一個(gè)包中包含一個(gè)或多個(gè)幀
ffmpeg 模塊
libavcodec
提供了一系列編碼器的實(shí)現(xiàn)
libavformat
實(shí)現(xiàn)了流協(xié)議、容器格式及其基本IO訪問(wèn)
libswscale
支持色彩轉(zhuǎn)換和縮放功能
libavfilter
提供了各種音視頻過(guò)濾器
libavdevice
提供了訪問(wèn)獲取設(shè)備和回放設(shè)備的接口
libswresample
實(shí)現(xiàn)了混音和重采樣
libavutil
包括了hash器,解碼器和各種工具函數(shù)



變量
封裝格式
AVFormatContext
這個(gè)類(lèi)是最頂層的結(jié)構(gòu)體,通過(guò)直接和流文件相對(duì)應(yīng)
AVFormatContext* pFormatCtx = avformat_alloc_context();
if(avformat_open_input(&pFormatCtx, filename, NULL, NULL) != 0)
{
printf("Can't find the stream!\n");
}
AVFormatContext 描述了媒體文件的構(gòu)成及基本信息,是統(tǒng)領(lǐng)全局的結(jié)構(gòu)體,很多函數(shù)都要用到它作為參數(shù),格式轉(zhuǎn)換過(guò)程中實(shí)現(xiàn)輸入和輸出功能、保存相關(guān)數(shù)據(jù)的主要結(jié)構(gòu),描述一個(gè)媒體文件或媒體流構(gòu)成和基本信息。
- nb_streams/streams:
AVStream結(jié)構(gòu)指針數(shù)組,包含了所有內(nèi)嵌媒體流的描述,其內(nèi)部有AVInputFormat和AVOutputFormat結(jié)構(gòu)體,來(lái)表示輸入輸出的文件格式
AVFormatContext是libavformat中非常重要的結(jié)構(gòu),它幾乎是ffmpeg中的一棵樹(shù),其成員AVStream可以包含0種或多種流nb_stream。
在AVStream中有可以包含已經(jīng)打開(kāi)的編解碼器codec,另外還有AVIOContext成員,這個(gè)成員的作用是io??梢灾貙?xiě)AVIOContext結(jié)構(gòu)的成員函數(shù)read_packet和write_packet等,來(lái)實(shí)現(xiàn)從不同介質(zhì)讀取音視頻數(shù)據(jù)(比如從網(wǎng)絡(luò)、內(nèi)存或磁盤(pán))
AVInputFormat
解復(fù)用器對(duì)象,每種輸入的封裝格式(FLV, MP4, TS等)對(duì)應(yīng)一個(gè)該結(jié)構(gòu)體
AVOutputFormat
復(fù)用器對(duì)象,每種輸出的封裝格式(FLV, MP4, TS等)對(duì)應(yīng)一個(gè)該結(jié)構(gòu)體
AVStream
用于描述一個(gè)媒體流,其中大部分信息可通過(guò)avformat_open_input根據(jù)頭文件信息確定,其他信息可通過(guò)avformat_find_stream_info獲得,典型的有視頻流、中英文視頻流、字幕流,可以通過(guò)av_new_stream或avformat_new_stream創(chuàng)建。
編解碼
AVCodecContext
描述編解碼器上下文的數(shù)據(jù)結(jié)構(gòu),保存AVCodec指針和與編碼器相關(guān)的數(shù)據(jù)
- codec_name
- height/width
- sample_fmt
- time_base
指向解碼器的指針
AVCodecContext* pCodecCtx = pFormatCtx->streams[videoindex]->codec;
AVCodec *pCodec = avcodec_find_decoder(pCodecCtx->codec_id);
它包含了當(dāng)前流媒體的幾乎所有參數(shù)(寬高,碼率)以及編解碼指針AVCodec,甚至還可以設(shè)置硬件加速相關(guān)(linux下的VAAPI),其中最重要的就是AVCodec,它直接指向編碼器實(shí)現(xiàn)。
AVCodec
AVCodec* pCodec = avcodec_find_decoder(pCodecCtx->codec_id);
if(pCodec == NULL){
printf("Cant't find the decoder !\n");
return -1;
}
解碼器對(duì)象本身,每個(gè)AVCodecContext中都包含一個(gè)AVCodec。
可以通過(guò)avcodec_find_decoder獲得
AVCodecParameters
// 獲取其中一個(gè)流的編碼器參數(shù)
AVCodecParameters pCodecpar = pFormatCtx->streams[i]->codecpar;
// 根據(jù)編碼器參數(shù)填充AVCodecContext
avcodec_parameters_to_context(pCodecCtx, pFormatCtx->streams[i]->codecpar);
編解碼參數(shù),每個(gè)AVStream中都含有一個(gè)AVCodecParameters,用來(lái)存放當(dāng)前流的編解碼參數(shù)。
數(shù)據(jù)存放
AVPacket
存放編碼后,解碼前的壓縮數(shù)據(jù),暫存解碼之前的媒體數(shù)據(jù)(一個(gè)音/視頻幀、一個(gè)字幕包)及附加信息(解碼時(shí)間戳、顯示時(shí)間戳、時(shí)長(zhǎng)等),主要用于建立緩沖區(qū)并裝載數(shù)據(jù)
- data/size/pos 數(shù)據(jù)緩沖區(qū)指針、長(zhǎng)度和流媒體中的偏移量
- flags
-
AV_PKT_FLAG_KEY表示該數(shù)據(jù)是一個(gè)關(guān)鍵幀 -
AV_PKT_FLAG_CORRUPT表示該數(shù)據(jù)已經(jīng)損壞
-
AVPacket* packet = (AVPacket*)malloc(sizeof(AVPacket));
if(av_read_frame(pFormatCtx, packet) >= 0){
...
}
AVFrame
AVFrame* pAvFrame = av_frame_alloc();
avcodec_decode_video2(pCodecCtx, pAvFrame, &got_picture, packet);
avcodec_decode_video2在新的API被拆解成兩個(gè)函數(shù)
ret = avcodec_send_packet(pCodecCtx, packet);
got_picture = avcodec_reveive_frame(pCodecCtx, pAvFrame);
存放編碼前,解碼后的原始數(shù)據(jù)
- data數(shù)組
- linesize
- key_frame 是否為關(guān)鍵幀
圖像轉(zhuǎn)換
SwsContext
圖像轉(zhuǎn)換上下文
libswscale模塊提供圖像縮放、圖像格式轉(zhuǎn)換功能。其中貫穿整個(gè)模塊的是SwsContext結(jié)構(gòu)體,方法包括sws_alloc_context分配、sws_init_context初始化、sws_getContext獲取上下文、sws_get_cachedContext獲取緩存,sws_freeContext釋放上下文的方法。
struct SwsContext *img_convert_ctx;
img_convert_ctx = sws_getContext(
pCodecCtx->width, // srcW
pCodecCtx->height, // dstW
pCodecCtx->pix_fmt,
pCodecCtx->width,
pCodecCtx->height,
AV_PIX_FMT_BGR24,
SWS_BICUBIC,
NULL, // srcFilter
NULL, // dstFilter
NULL
);
函數(shù)
av_register_all()
初始化ffmpeg的環(huán)境,過(guò)時(shí)的API,新版本不需要了
avformat_alloc_context()
分配一個(gè)AVFromatContext
AVFormatContext *pFormatCtx = avformat_alloc_context();
avformat_open_input()
https://ffmpeg.org/doxygen/4.1/group__lavf__decoding.html#ga31d601155e9035d5b0e7efedc894ee49
根據(jù)輸入視頻,獲取AVCodecContext。
if(avformat_open_input(&pFormatCtx, NULL) < 0){
printf("Can't find the stream!\n");
}
avformat_find_stream_info()
探測(cè)是否有AVFormatContext中是否有stream存在
avcodec_alloc_context3()
為AVCodecContext分配內(nèi)存,并利用傳入的AVCodec初始化
avcodec_parameters_to_context()
獲取AVCodecParameters
avcodec_find_decoder()
查找解碼器
AVCodec *pCodec = avcodec_find_decoder(pCodecCtx->codec_id);
avcodec_open2()
打開(kāi)編碼器
if (avcodec_open2(pCodecCtx, pCodec, NULL) < 0) {
printf("Can't open the decoder !\n");
return -1;
}
av_frame_alloc()
為幀分配空間
AVFrame *pFrameBGR = av_frame_alloc();
av_image_get_buffer_size()
根據(jù)AVCodeContext的記錄的長(zhǎng)寬高,計(jì)算buffer的大小
int size = av_image_get_buffer_size(AV_PIX_FMT_BGR24, pCodecCtx->width, pCodecCtx->height, 1);
av_image_fill_arrays()
填充AVFrame的數(shù)據(jù)
AVFrame *pFrameBGR = av_frame_alloc();
uint8_t *out_buffer = (uint8_t *)av_malloc(size);
av_image_fill_arrays(pFrameBRG->data, pFrameBRG->linesize, out_buffer, AV_PIX_FMT_BGR24, pCodecCtx->width, pCodecCtx->height, 1);
av_dump_format()
打印AVFormatContext和對(duì)應(yīng)文件的信息
av_dump_format(pFormatCtx, 0, filename, 0);
sws_getContext()
獲取SwsContext上下文
SwsContext *img_convert_ctx;
img_convert_ctx = sws_getContext(
pCodecCtx->width,
pCodecCtx->pix_fmt,
pCodecCtx->width,
pCodecCtx->height,
AV_PIX_FMT_BGR24, // 設(shè)置sws_scale轉(zhuǎn)換格式為BGR24,這樣轉(zhuǎn)換后可以直接使用OpenCV查看圖像
SWS_BICUBIC,
NULL, // SwsFilter* srcFilter
NULL, // SwsFilter* dstFilter
NULL
);
av_read_frame()
從AVFormatContext讀取一個(gè)AVPacket
AVPacket* packet = (AVPacket*)malloc(sizeof(AVPacket));
if(av_read_frame(pFormatCtx, packet) >= 0){
...
}
avcodec_decode_video2()
這個(gè)函數(shù)過(guò)時(shí)了,可以替換為avcode_send_packet和avcodec_receive_frame
從AVpacket讀取一幀到AVFrame中
int ret = avcodec_decode_video2(pCodecCtx, pAvFrame, &got_picture, packet);
avcode_send_packet()
發(fā)送編碼數(shù)據(jù)包
ret = avcodec_send_packet(pCodecCtx, packet);
avcodec_receive_frame()
接收編碼后的數(shù)據(jù)
got_picture = avcodec_receive_frame(pCodeCtx, pAvFrame);
sws_scale
將一個(gè)AVFrame的data轉(zhuǎn)換為另一個(gè)AVFrame的data
sws_scale(
img_convert_ctx,
(const uint8_t* const*)pAvFrame->data,
pAvFrame->lineSize,
0,
pCodecCtx->height,
pFrameBGR->data,
pFrameBGR->linesize,
);
av_packet_unref()
av_packet_unfer(packet);
av_free()
// uint8_t *out_buffer = (uint8_t *)av_malloc(size);
av_free(out_buffer);
// pAvFrame = av_frame_alloc();
av_free(pAvFrame);
avcodec_close()
avcodec_close(pCodecCtx);
avformat_close_input()
avformat_close_input(&pFormatCtx);
sws_freeContext()
sws_freeContext(img_convert_ctx);
一個(gè)跑通的ffmpeg轉(zhuǎn)opencv的demo
舊API
// https://blog.csdn.net/guyuealian/article/details/79607568
#define __STDC_CONSTANT_MACROS
#include <opencv2/opencv.hpp>
extern "C"
{
#include "libavutil/avutil.h"
#include "libavcodec/avcodec.h"
#include "libavformat/avformat.h"
#include "libswscale/swscale.h" //新版里的圖像轉(zhuǎn)換結(jié)構(gòu)需要引入的頭文件
}
#include <iostream>
const char* filename = "/home/cf206/下載/palace.mp4";
int main()
{
AVCodec *pCodec; //解碼器指針
AVCodecContext* pCodecCtx; //ffmpeg解碼類(lèi)的類(lèi)成員
AVFrame* pAvFrame; //多媒體幀,保存解碼后的數(shù)據(jù)幀
AVFormatContext* pFormatCtx; //保存視頻流的信息
av_register_all();
pFormatCtx = avformat_alloc_context();
if (avformat_open_input(&pFormatCtx, filename, NULL, NULL) != 0) { //檢查文件頭部
printf("Can't find the stream!\n");
}
if (avformat_find_stream_info(pFormatCtx, NULL)<0) { //查找流信息
printf("Can't find the stream information !\n");
}
int videoindex = -1;
for (int i = 0; i < pFormatCtx->nb_streams; ++i) //遍歷各個(gè)流,找到第一個(gè)視頻流,并記錄該流的編碼信息
{
if (pFormatCtx->streams[i]->codec->codec_type == AVMEDIA_TYPE_VIDEO) {
videoindex = i;
break;
}
}
if (videoindex == -1) {
printf("Don't find a video stream !\n");
return -1;
}
pCodecCtx = pFormatCtx->streams[videoindex]->codec; //得到一個(gè)指向視頻流的上下文指針
pCodec = avcodec_find_decoder(pCodecCtx->codec_id); //到該格式的解碼器
if (pCodec == NULL) {
printf("Cant't find the decoder !\n"); //尋找解碼器
return -1;
}
if (avcodec_open2(pCodecCtx, pCodec, NULL) < 0) { //打開(kāi)解碼器
printf("Can't open the decoder !\n");
return -1;
}
pAvFrame = av_frame_alloc(); // 分配幀存儲(chǔ)空間
AVFrame *pFrameBGR = av_frame_alloc(); // 存儲(chǔ)解碼后的BGR數(shù)據(jù)
int size = avpicture_get_size(AV_PIX_FMT_BGR24, pCodecCtx->width, pCodecCtx->height);
uint8_t *out_buffer = (uint8_t *)av_malloc(size);
avpicture_fill((AVPicture *)pFrameBGR, out_buffer, AV_PIX_FMT_BGR24, pCodecCtx->width, pCodecCtx->height);
AVPacket* packet = (AVPacket*)malloc(sizeof(AVPacket));
printf("-----------輸出文件信息---------\n");
av_dump_format(pFormatCtx, 0, filename, 0);
printf("------------------------------");
struct SwsContext *img_convert_ctx;
img_convert_ctx = sws_getContext(pCodecCtx->width,
pCodecCtx->height,
pCodecCtx->pix_fmt,
pCodecCtx->width,
pCodecCtx->height,
AV_PIX_FMT_BGR24, //設(shè)置sws_scale轉(zhuǎn)換格式為BGR24,這樣轉(zhuǎn)換后可以直接用OpenCV顯示圖像了
SWS_BICUBIC,
NULL, NULL, NULL);
int ret;
int got_picture;
for (;;)
{
if (av_read_frame(pFormatCtx, packet) >= 0)
{
if (packet->stream_index == videoindex)
{
ret = avcodec_decode_video2(pCodecCtx, pAvFrame, &got_picture, packet);
if (ret < 0)
{
printf("Decode Error.(解碼錯(cuò)誤)\n");
return -1;
}
if (got_picture)
{
//YUV to RGB
sws_scale(img_convert_ctx,
(const uint8_t* const*)pAvFrame->data,
pAvFrame->linesize,
0,
pCodecCtx->height,
pFrameBGR->data,
pFrameBGR->linesize);
cv::Mat mRGB(cv::Size(pCodecCtx->width, pCodecCtx->height), CV_8UC3);
mRGB.data =(uchar*)pFrameBGR->data[0];//注意不能寫(xiě)為:(uchar*)pFrameBGR->data
cv::imshow("RGB", mRGB);
cv::waitKey(40);
}
}
av_free_packet(packet);
}
else
{
break;
}
}
av_free(out_buffer);
av_free(pFrameBGR);
av_free(pAvFrame);
avcodec_close(pCodecCtx);
avformat_close_input(&pFormatCtx);
sws_freeContext(img_convert_ctx);
system("pause");
}
新API
改寫(xiě)成新版ffmpeg的API
#define __STDC_CONSTANT_MACROS
#include <opencv2/opencv.hpp>
extern "C"
{
#include "libavutil/avutil.h"
#include "libavcodec/avcodec.h"
#include "libavutil/imgutils.h"
#include "libavformat/avformat.h"
#include "libswscale/swscale.h" //新版里的圖像轉(zhuǎn)換結(jié)構(gòu)需要引入的頭文件
}
#include <iostream>
const char* filename = "/home/tuo/Videos/tracking.mp4";
int main()
{
AVCodec *pCodec; //解碼器指針
AVCodecContext* pCodecCtx; //ffmpeg解碼類(lèi)的類(lèi)成員
AVFrame* pAvFrame; //多媒體幀,保存解碼后的數(shù)據(jù)幀
AVFormatContext* pFormatCtx; //保存視頻流的信息
// av_register_all();
pFormatCtx = avformat_alloc_context();
if (avformat_open_input(&pFormatCtx, filename, NULL, NULL) != 0) { //檢查文件頭部
printf("Can't find the stream!\n");
}
if (avformat_find_stream_info(pFormatCtx, NULL)<0) { //查找流信息
printf("Can't find the stream information !\n");
}
// int videoindex = -1;
// for (int i = 0; i < pFormatCtx->nb_streams; ++i) //遍歷各個(gè)流,找到第一個(gè)視頻流,并記錄該流的編碼信息
// {
// if (pFormatCtx->streams[i]->codec->codec_type == AVMEDIA_TYPE_VIDEO) {
// videoindex = i;
// break;
// }
// }
pCodecCtx = avcodec_alloc_context3(NULL);
if (pCodecCtx == NULL)
{
printf("Could not allocate AVCodecContext\n");
return -1;
}
int videoindex = -1;
for (int i = 0; i < pFormatCtx->nb_streams; ++i) //遍歷各個(gè)流,找到第一個(gè)視頻流,并記錄該流的編碼信息
{
avcodec_parameters_to_context(pCodecCtx, pFormatCtx->streams[i]->codecpar);
if (pCodecCtx->codec_type == AVMEDIA_TYPE_VIDEO) {
videoindex = i;
break;
}
}
if (videoindex == -1) {
printf("Don't find a video stream !\n");
return -1;
}
pCodec = avcodec_find_decoder(pCodecCtx->codec_id); //到該格式的解碼器
if (pCodec == NULL) {
printf("Cant't find the decoder !\n"); //尋找解碼器
return -1;
}
if (avcodec_open2(pCodecCtx, pCodec, NULL) < 0) { //打開(kāi)解碼器
printf("Can't open the decoder !\n");
return -1;
}
pAvFrame = av_frame_alloc(); // 分配幀存儲(chǔ)空間
AVFrame *pFrameBGR = av_frame_alloc(); // 存儲(chǔ)解碼后的BGR數(shù)據(jù)
// int size = avpicture_get_size(AV_PIX_FMT_BGR24, pCodecCtx->width, pCodecCtx->height);
int size = av_image_get_buffer_size(AV_PIX_FMT_BGR24, pCodecCtx->width, pCodecCtx->height, 1);
uint8_t *out_buffer = (uint8_t *)av_malloc(size);
// avpicture_fill((AVPicture *)pFrameBGR, out_buffer, AV_PIX_FMT_BGR24, pCodecCtx->width, pCodecCtx->height);
av_image_fill_arrays(pFrameBGR->data, pFrameBGR->linesize, out_buffer, AV_PIX_FMT_BGR24, pCodecCtx->width, pCodecCtx->height, 1);
AVPacket* packet = (AVPacket*)malloc(sizeof(AVPacket));
printf("-----------輸出文件信息---------\n");
av_dump_format(pFormatCtx, 0, filename, 0);
printf("------------------------------");
std::cout << pCodecCtx->height << " " << pCodecCtx->width << std::endl;
struct SwsContext *img_convert_ctx;
img_convert_ctx = sws_getContext(pCodecCtx->width,
pCodecCtx->height,
pCodecCtx->pix_fmt,
pCodecCtx->width,
pCodecCtx->height,
AV_PIX_FMT_BGR24, //設(shè)置sws_scale轉(zhuǎn)換格式為BGR24,這樣轉(zhuǎn)換后可以直接用OpenCV顯示圖像了
SWS_BICUBIC,
NULL, NULL, NULL);
int ret;
int got_picture;
for (;;)
{
if (av_read_frame(pFormatCtx, packet) >= 0)
{
if (packet->stream_index == videoindex)
{
//ret = avcodec_decode_video2(pCodecCtx, pAvFrame, &got_picture, packet);
ret = avcodec_send_packet(pCodecCtx, packet);
got_picture = avcodec_receive_frame(pCodecCtx, pAvFrame);
if (ret < 0)
{
printf("Decode Error.(解碼錯(cuò)誤)\n");
return -1;
}
if (got_picture == 0)
{
//YUV to RGB
sws_scale(img_convert_ctx,
(const uint8_t* const*)pAvFrame->data,
pAvFrame->linesize,
0,
pCodecCtx->height,
pFrameBGR->data,
pFrameBGR->linesize);
cv::Mat mRGB(cv::Size(pCodecCtx->width, pCodecCtx->height), CV_8UC3);
mRGB.data =(uchar*)pFrameBGR->data[0];//注意不能寫(xiě)為:(uchar*)pFrameBGR->data
cv::imshow("RGB", mRGB);
cv::waitKey(40);
}
}
// av_free_packet(packet);
av_packet_unref(packet);
}
else
{
break;
}
}
av_free(out_buffer);
av_free(pFrameBGR);
av_free(pAvFrame);
avcodec_close(pCodecCtx);
avformat_close_input(&pFormatCtx);
sws_freeContext(img_convert_ctx);
system("pause");
}