ffmpeg/libx264 C API:从短MP4结尾处丢弃的帧 在C++应用程序中,我使用一系列JPEG图像,使用FieldIm操作它们的数据,然后使用FFMPEG/LIBX264 C API将位图编码为H264。输出是一个MP4,以12fps的速度显示22幅图像。我的代码改编自ffmpeg C源代码附带的“muxing”示例
我的问题是:无论我如何调整编解码器参数,传递给编码器的序列末尾的一定数量的帧不会出现在最终输出中。我已将AVCodecContext参数设置为:ffmpeg/libx264 C API:从短MP4结尾处丢弃的帧 在C++应用程序中,我使用一系列JPEG图像,使用FieldIm操作它们的数据,然后使用FFMPEG/LIBX264 C API将位图编码为H264。输出是一个MP4,以12fps的速度显示22幅图像。我的代码改编自ffmpeg C源代码附带的“muxing”示例,c++,c,ffmpeg,mp4,libx264,C++,C,Ffmpeg,Mp4,Libx264,我的问题是:无论我如何调整编解码器参数,传递给编码器的序列末尾的一定数量的帧不会出现在最终输出中。我已将AVCodecContext参数设置为: //set context params ctx->codec_id = AV_CODEC_ID_H264; ctx->bit_rate = 4000 * 1000; ctx->width = _width; ctx->height = _height; ost->st->time_base = AVRational
//set context params
ctx->codec_id = AV_CODEC_ID_H264;
ctx->bit_rate = 4000 * 1000;
ctx->width = _width;
ctx->height = _height;
ost->st->time_base = AVRational{ 1, 12 };
ctx->time_base = ost->st->time_base;
ctx->gop_size = 1;
ctx->pix_fmt = AV_PIX_FMT_YUV420P;
我发现,gop_大小越大
视频结束时丢弃的帧越多。我还可以从输出中看到,对于这个gop大小(我基本上指示所有输出帧都是I帧),只写入了9帧
我不知道为什么会发生这种情况。我尝试对重复帧进行编码,并制作更长的视频。这导致没有帧被丢弃。我知道,使用ffmpeg命令行工具,有一个连接命令可以完成我试图做的事情,但我不确定如何使用capi实现相同的目标
以下是我从控制台获得的输出:
[libx264@026d81c0]使用cpu功能:MMX2 SSE2Fast SSSE3
SSE4.2 AVX FMA3 BMI2 AVX2[libx264@026d81c0]配置文件高,级别
3.1[libx264@026d81c0]264-核心152 r2851 ba24899-H.264/MPEG-4 AVC编解码器-Cop yleft 2003-2017--
选项:cabac=1 ref=1 deb lock=1:0:0 analysis=0x3:0x113 me=hex submi=7
psy=1 psy\u rd=1.00:0.00混合\u ref=0 m e\u范围=16色度\u me=1网格=1
8x8dct=1 cqm=0死区=21,11快速pskip=1 chro ma\U qp\U偏移量=-2
线程=12个前瞻线程=2个切片线程=0 nr=0抽取=1
隔行=0模糊\u兼容=0约束\u帧内=0 b帧=0权重p=0
keyint=1 keyint\u min=1 scenecut=40帧内刷新=0 rc=abr mbtree=0
比特率=4000比特率TOL=1.0 qcomp=0.60 qpmin=0 qpmax=69 qpstep=4
ip#U比率=1.40 aq=1:1.00输出#0,mp4,至
“....\images\c411a991-46f6-400c-8bb0-77af3738559a.mp4”:
流#0:0:视频:h264,yuv420p,700x700,q=2-314000 kb/s,12 tbn
[libx264@026d81c0]帧I:9平均QP:17.83尺寸:111058[libx264
@026d81c0]mb I I16..4:1.9%47.7%50.5%[libx264@026d81c0]最终版本
比率系数:19.14[libx264@026d81c0]8x8变换内部:47.7%
[libx264@026d81c0]编码y、uvDC、uvAC内部:98.4%96.9%89.5%
[libx264@026d81c0]i16 v,h,dc,p:64%6%2%28%@
026d81c0]i8 v、h、dc、ddl、ddr、vr、hd、vl、hu:32%15%9%5%5%6%8%
10%10%[libx264@026d81c0]i4 v、h、dc、ddl、ddr、vr、hd、vl、hu:28%18%
7%6%8%8%8%9%8%[libx264@026d81c0]i8c直流、高压、高压、高压:43%22%
25%10%[libx264@026d81c0]kb/s:10661.53
代码如下:
MP4Writer.h
#ifndef MPEG_WRITER
#define MPEG_WRITER
#include <iostream>
#include <string>
#include <vector>
#include <ImgData.h>
extern "C" {
#include <libavformat/avformat.h>
#include <libswscale/swscale.h>
#include <libswresample/swresample.h>
#include <libswscale/swscale.h>
}
typedef struct OutputStream
{
AVStream *st;
AVCodecContext *enc;
//pts of the next frame that will be generated
int64_t next_pts;
int samples_count;
AVFrame *frame;
AVFrame *tmp_frame;
float t, tincr, tincr2;
struct SwsContext *sws_ctx;
struct SwrContext *swr_ctx;
};
class MP4Writer {
public:
MP4Writer();
void Init();
int16_t SetOutput( const std::string & path );
int16_t AddFrame( uint8_t * imgData );
int16_t Write( std::vector<ImgData> & imgData );
int16_t Finalize();
void SetHeight( const int height ) { _height = _width = height; } //assuming 1:1 aspect ratio
private:
int16_t AddStream( OutputStream * ost, AVFormatContext * formatCtx, AVCodec ** codec, enum AVCodecID codecId );
int16_t OpenVideo( AVFormatContext * formatCtx, AVCodec *codec, OutputStream * ost, AVDictionary * optArg );
static AVFrame * AllocPicture( enum AVPixelFormat pixFmt, int width, int height );
static AVFrame * GetVideoFrame( uint8_t * imgData, OutputStream * ost, const int width, const int height );
static int WriteFrame( AVFormatContext * formatCtx, const AVRational * timeBase, AVStream * stream, AVPacket * packet );
int _width;
int _height;
OutputStream _ost;
AVFormatContext * _formatCtx;
AVDictionary * _dict;
};
#endif //MPEG_WRITER
\ifndef-MPEG\u编写器
#定义MPEG_编写器
#包括
#包括
#包括
#包括
外部“C”{
#包括
#包括
#包括
#包括
}
typedef结构输出流
{
AVStream*st;
AVCodecContext*enc;
//将生成的下一帧的pts
int64_t next_pts;
int样本计数;
AVFrame*frame;
AVFrame*tmp_帧;
浮子t、tincr、tincr2;
结构SwsContext*sws_ctx;
结构SwrContext*swr_ctx;
};
MP4Writer类{
公众:
MP4Writer();
void Init();
int16_t SetOutput(常量标准::字符串和路径);
int16添加帧(uint8*imgData);
int16_t Write(标准::向量和imgData);
int16_t Finalize();
void SetHeight(const int height){{u height=\u width=height;}//假设高宽比为1:1
私人:
int16_t AddStream(OutputStream*ost、AVFormatContext*formatCtx、AVCodec**codec、enum AVCodecID codecId);
int16_t OpenVideo(AVFormatContext*formatCtx、AVCodec*codec、OutputStream*ost、AVDictionary*optArg);
静态AVFrame*AllocPicture(枚举AVPixelFormat pixFmt,整数宽度,整数高度);
静态AVFrame*GetVideoFrame(uint8_t*imgData、OutputStream*ost、常量整数宽度、常量整数高度);
静态int WriteFrame(AVFormatContext*formatCtx,const AVRational*timeBase,AVStream*stream,AVPacket*packet);
内部宽度;
内部高度;
前哨站;
AVFormatContext*_formatCtx;
AVDictionary*_dict;
};
#endif//MPEG\u编写器
MP4Writer.cpp
#include <MP4Writer.h>
#include <algorithm>
MP4Writer::MP4Writer()
{
_width = 0;
_height = 0;
}
void MP4Writer::Init()
{
av_register_all();
}
/**
sets up output stream for the specified path.
note that the output format is deduced automatically from the file extension passed
@param path: output file path
@returns: -1 = output could not be deduced, -2 = invalid codec, -3 = error opening output file,
-4 = error writing header
*/
int16_t MP4Writer::SetOutput( const std::string & path )
{
int error;
AVCodec * codec;
AVOutputFormat * format;
_ost = OutputStream{}; //TODO reset state in a more focused way?
//allocate output media context
avformat_alloc_output_context2( &_formatCtx, NULL, NULL, path.c_str() );
if ( !_formatCtx ) {
std::cout << "could not deduce output format from file extension. aborting" << std::endl;
return -1;
}
//set format
format = _formatCtx->oformat;
if ( format->video_codec != AV_CODEC_ID_NONE ) {
AddStream( &_ost, _formatCtx, &codec, format->video_codec );
}
else {
std::cout << "there is no video codec set. aborting" << std::endl;
return -2;
}
OpenVideo( _formatCtx, codec, &_ost, _dict );
av_dump_format( _formatCtx, 0, path.c_str(), 1 );
//open output file
if ( !( format->flags & AVFMT_NOFILE )) {
error = avio_open( &_formatCtx->pb, path.c_str(), AVIO_FLAG_WRITE );
if ( error < 0 ) {
std::cout << "there was an error opening output file " << path << ". aborting" << std::endl;
return -3;
}
}
//write header
error = avformat_write_header( _formatCtx, &_dict );
if ( error < 0 ) {
std::cout << "an error occurred writing header. aborting" << std::endl;
return -4;
}
return 0;
}
/**
initialize the output stream
@param ost: the output stream
@param formatCtx: the context format
@param codec: the output codec
@param codec: the ffmpeg enumerated id of the codec
@returns: -1 = encoder not found, -2 = stream could not be allocated, -3 = encoding context could not be allocated
*/
int16_t MP4Writer::AddStream( OutputStream * ost, AVFormatContext * formatCtx, AVCodec ** codec, enum AVCodecID codecId )
{
AVCodecContext * ctx; //TODO not sure why this is here, could just set ost->enc directly
int i;
//detect the encoder
*codec = avcodec_find_encoder( codecId );
if ( (*codec) == NULL ) {
std::cout << "could not find encoder. aborting" << std::endl;
return -1;
}
//allocate stream
ost->st = avformat_new_stream( formatCtx, NULL );
if ( ost->st == NULL ) {
std::cout << "could not allocate stream. aborting" << std::endl;
return -2;
}
//allocate encoding context
ost->st->id = formatCtx->nb_streams - 1;
ctx = avcodec_alloc_context3( *codec );
if ( ctx == NULL ) {
std::cout << "could not allocate encoding context. aborting" << std::endl;
return -3;
}
ost->enc = ctx;
//set context params
ctx->codec_id = AV_CODEC_ID_H264;
ctx->bit_rate = 4000 * 1000;
ctx->width = _width;
ctx->height = _height;
ost->st->time_base = AVRational{ 1, 12 };
ctx->time_base = ost->st->time_base;
ctx->gop_size = 1;
ctx->pix_fmt = AV_PIX_FMT_YUV420P;
//if neccesary, set stream headers and formats separately
if ( formatCtx->oformat->flags & AVFMT_GLOBALHEADER ) {
std::cout << "setting stream and headers to be separate" << std::endl;
ctx->flags |= AV_CODEC_FLAG_GLOBAL_HEADER;
}
return 0;
}
/**
open the video for writing
@param formatCtx: the format context
@param codec: output codec
@param ost: output stream
@param optArg: dictionary
@return: -1 = error opening codec, -2 = allocate new frame, -3 = copy stream params
*/
int16_t MP4Writer::OpenVideo( AVFormatContext * formatCtx, AVCodec *codec, OutputStream * ost, AVDictionary * optArg )
{
int error;
AVCodecContext * ctx = ost->enc;
AVDictionary * dict = NULL;
av_dict_copy( &dict, optArg, 0 );
//open codec
error = avcodec_open2( ctx, codec, &dict );
av_dict_free( &dict );
if ( error < 0 ) {
std::cout << "there was an error opening the codec. aborting" << std::endl;
return -1;
}
//allocate new frame
ost->frame = AllocPicture( ctx->pix_fmt, ctx->width, ctx->height );
if ( ost->frame == NULL ) {
std::cout << "there was an error allocating a new frame. aborting" << std::endl;
return -2;
}
//copy steam params
error = avcodec_parameters_from_context( ost->st->codecpar, ctx );
if ( error < 0 ) {
std::cout << "could not copy stream parameters. aborting" << std::endl;
return -3;
}
return 0;
}
/**
allocate a new frame
@param pixFmt: ffmpeg enumerated pixel format
@param width: output width
@param height: output height
@returns: an inititalized frame
*/
AVFrame * MP4Writer::AllocPicture( enum AVPixelFormat pixFmt, int width, int height )
{
AVFrame * picture;
int error;
//allocate the frame
picture = av_frame_alloc();
if ( picture == NULL ) {
std::cout << "there was an error allocating the picture" << std::endl;
return NULL;
}
picture->format = pixFmt;
picture->width = width;
picture->height = height;
//allocate the frame's data buffer
error = av_frame_get_buffer( picture, 32 );
if ( error < 0 ) {
std::cout << "could not allocate frame data" << std::endl;
return NULL;
}
picture->pts = 0;
return picture;
}
/**
convert raw RGB buffer to YUV frame
@return: frame that contains image data
*/
AVFrame * MP4Writer::GetVideoFrame( uint8_t * imgData, OutputStream * ost, const int width, const int height )
{
int error;
AVCodecContext * ctx = ost->enc;
//prepare the frame
error = av_frame_make_writable( ost->frame );
if ( error < 0 ) {
std::cout << "could not make frame writeable" << std::endl;
return NULL;
}
//TODO set this context one time per run, or even better, one time at init
//convert RGB to YUV
struct SwsContext* fooContext = sws_getContext( width, height, AV_PIX_FMT_BGR24,
width, height, AV_PIX_FMT_YUV420P, SWS_BICUBIC, NULL, NULL, NULL );
int inLinesize[1] = { 3 * width }; // RGB stride
uint8_t * inData[1] = { imgData };
int sliceHeight = sws_scale( fooContext, inData, inLinesize, 0, height, ost->frame->data, ost->frame->linesize );
sws_freeContext( fooContext );
ost->frame->pts = ost->next_pts++;
//TODO does the frame need to be returned here as it is available at the class level?
return ost->frame;
}
/**
write frame to file
@param formatCtx: the output format context
@param timeBase: the framerate
@param stream: output stream
@param packet: data packet
@returns: see return values for av_interleaved_write_frame
*/
int MP4Writer::WriteFrame( AVFormatContext * formatCtx, const AVRational * timeBase, AVStream * stream, AVPacket * packet )
{
av_packet_rescale_ts( packet, *timeBase, stream->time_base );
packet->stream_index = stream->index;
//write compressed file to media file
return av_interleaved_write_frame( formatCtx, packet );
}
int16_t MP4Writer::Write( std::vector<ImgData> & imgData )
{
int16_t errorCount = 0;
int16_t retVal = 0;
bool countingUp = true;
size_t i = 0;
while ( true ) {
//don't show first frame again when counting back down
if ( !countingUp && i == 0 ) {
break;
}
uint8_t * pixels = imgData[i].GetBits( imgData[i].mp4Input );
AddFrame( pixels );
//handle inc/dec without repeating last frame
if ( countingUp ) {
if ( i == imgData.size() -1 ) {
countingUp = false;
i--;
}
else {
i++;
}
}
else {
i--;
}
}
Finalize();
return 0; //TODO return error code
}
/**
add another frame to output video
@param imgData: the raw image data
@returns -1 = error encoding video frame, -2 = error writing frame
*/
int16_t MP4Writer::AddFrame( uint8_t * imgData )
{
int error;
AVCodecContext * ctx;
AVFrame * frame;
int gotPacket = 0;
AVPacket pkt = { 0 };
ctx = _ost.enc;
av_init_packet( &pkt );
frame = GetVideoFrame( imgData, &_ost, _width, _height );
//encode the image
error = avcodec_encode_video2( ctx, &pkt, frame, &gotPacket );
if ( error < 0 ) {
std::cout << "there was an error encoding the video frame" << std::endl;
return -1;
}
//write the frame. NOTE: this doesn't kick in until the encoder has received a certain number of frames
if ( gotPacket ) {
error = WriteFrame( _formatCtx, &ctx->time_base, _ost.st, &pkt );
if ( error < 0 ) {
std::cout << "the video frame could not be written" << std::endl;
return -2;
}
}
return 0;
}
/**
finalize output video and cleanup
*/
int16_t MP4Writer::Finalize()
{
av_write_trailer( _formatCtx );
avcodec_free_context( &_ost.enc );
av_frame_free( &_ost.frame);
av_frame_free( &_ost.tmp_frame );
avio_closep( &_formatCtx->pb );
avformat_free_context( _formatCtx );
sws_freeContext( _ost.sws_ctx );
swr_free( &_ost.swr_ctx);
return 0;
}
#包括
#包括
MP4Writer::MP4Writer()
{
_宽度=0;
_高度=0;
}
void MP4Writer::Init()
{
av_寄存器_all();
}
/**
设置指定路径的输出流。
请注意,输出格式是根据传递的文件扩展名自动推导出来的
@参数路径:输出文件路径
@返回:-1=无法推断输出,-2=编解码器无效,-3=打开输出文件时出错,
-4=写入标题时出错
*/
int16_t MP4Writer::SetOutput(常量std::字符串和路径)
{
整数误差;
AVCodec*编解码器;
AVOutputFormat*格式;
_ost=OutputStream{};//是否以更集中的方式重置状态?
//分配输出媒体上下文
avformat_alloc_output_context2(&_formatCtx,NULL,NULL,path.c_str());
如果(!\u formatCtx){
std::cout video\u codec!=AV\u codec\u ID\u NONE){
AddStream(&_ost,_formatCtx,&codec,format->video\u codec);
}
否则{
std::cout-pb,path.c_-str(),AVIO_-FLAG_-WRITE);
如果(错误<0){
std::cout st->time_base=AVRational{1,12};
ctx->time\u base=ost->st->time\u base;
ctx->gop_尺寸=1;
ctx->pix\U fmt=AV\U pix\U fmt\U YUV420P;
//如有必要,单独设置流标题和格式
if(formatCtx->oformat->flags和AVFMT_GLOBALHEADER){
std::cout-enc;
AVDictionary*dict=NULL;
影音副本(&dict,optArg,0);
//开放式编解码器
错误=avcodec_open2(ctx、codec和dict);
av_dict_free(&dict);
如果(错误<0){
标准:cout pix_fmt,ctx->宽度,ctx->高度)
#include <FreeImage.h>
#include <MP4Writer.h>
#include <vector>
struct ImgData
{
unsigned int width;
unsigned int height;
std::string path;
FIBITMAP * mp4Input;
uint8_t * GetBits( FIBITMAP * bmp ) { return FreeImage_GetBits( bmp ); }
};
int main()
{
std::vector<ImgData> imgDataVec;
//load images and push to imgDataVec
MP4Writer mp4Writer;
mp4Writer.SetHeight( 1200 ); //assumes 1:1 aspect ratio
mp4Writer.Init();
mp4Writer.SetOutput( "test.mp4" );
mp4Writer.Write( imgDataVec );
}