我想使用ffmpeg从QImage生成GIF - 所有这些以编程方式(C ++)。我正在使用Qt 5.6和ffmpeg的最后一次构建(构建git-0a9e781(2016-06-10)。
我已经能够在.mp4中转换这些QImage并且它可以工作。我尝试对GIF使用相同的原理,改变格式像素和编解码器。 GIF由两张图片(每张1秒)生成,15 FPS。
## INITIALIZATION
#####################################################################
// Filepath : "C:/Users/.../qt_temp.Jv7868.gif"
// Allocating an AVFormatContext for an output format...
avformat_alloc_output_context2(formatContext, NULL, NULL, filepath);
...
// Adding the video streams using the default format codecs and initializing the codecs.
stream = avformat_new_stream(formatContext, *codec);
AVCodecContext * codecContext = avcodec_alloc_context3(*codec);
context->codec_id = codecId;
context->bit_rate = 400000;
...
context->pix_fmt = AV_PIX_FMT_BGR8;
...
// Opening the codec...
avcodec_open2(codecContext, codec, NULL);
...
frame = allocPicture(codecContext->width, codecContext->height, codecContext->pix_fmt);
tmpFrame = allocPicture(codecContext->width, codecContext->height, AV_PIX_FMT_RGBA);
...
avformat_write_header(formatContext, NULL);
## ADDING A NEW FRAME
#####################################################################
// Getting in parameter the QImage: newFrame(const QImage & image)
const qint32 width = image.width();
const qint32 height = image.height();
// Converting QImage into AVFrame
for (qint32 y = 0; y < height; y++) {
const uint8_t * scanline = image.scanLine(y);
for (qint32 x = 0; x < width * 4; x++) {
tmpFrame->data[0][y * tmpFrame->linesize[0] + x] = scanline[x];
}
}
...
// Scaling...
if (codec->pix_fmt != AV_PIX_FMT_BGRA) {
if (!swsCtx) {
swsCtx = sws_getContext(codec->width, codec->height,
AV_PIX_FMT_BGRA,
codec->width, codec->height,
codec->pix_fmt,
SWS_BICUBIC, NULL, NULL, NULL);
}
sws_scale(swsCtx,
(const uint8_t * const *)tmpFrame->data,
tmpFrame->linesize,
0,
codec->height,
frame->data,
frame->linesize);
}
frame->pts = nextPts++;
...
int gotPacket = 0;
AVPacket packet = {0};
av_init_packet(&packet);
avcodec_encode_video2(codec, &packet, frame, &gotPacket);
if (gotPacket) {
av_packet_rescale_ts(paket, *codec->time_base, stream->time_base);
paket->stream_index = stream->index;
av_interleaved_write_frame(formatContext, paket);
}
但是当我尝试修改视频编解码器和像素格式以符合GIF规范时,我遇到了一些问题。
我尝试了几个编解码器,例如AV_CODEC_ID_GIF
和AV_CODEC_ID_RAWVIDEO
,但它们似乎都不起作用。在初始化阶段,avcodec_open2()
始终返回此类错误:
Specified pixel format rgb24 is invalid or not supported
Could not open video codec: gif
编辑2016年6月17日
再挖一点,avcodec_open2()
返回-22:
#define EINVAL 22 /* Invalid argument */
编辑22/06/2016
以下是用于编译ffmpeg的标志:
"FFmpeg/Libav configuration: --disable-static --enable-shared --enable-gpl --enable-version3 --disable-w32threads --enable-nvenc --enable-avisynth --enable-bzlib --enable-fontconfig --enable-frei0r --enable-gnutls --enable-iconv --enable-libass --enable-libbluray --enable-libbs2b --enable-libcaca --enable-libfreetype --enable-libgme --enable-libgsm --enable-libilbc --enable-libmodplug --enable-libmfx --enable-libmp3lame --enable-libopencore-amrnb --enable-libopencore-amrwb --enable-libopenjpeg --enable-libopus --enable-librtmp --enable-libschroedinger --enable-libsnappy --enable-libsoxr --enable-libspeex --enable-libtheora --enable-libtwolame --enable-libvidstab --enable-libvo-amrwbenc --enable-libvorbis --enable-libvpx --enable-libwavpack --enable-libwebp --enable-libx264 --enable-libx265 --enable-libxavs --enable-libxvid --enable-libzimg --enable-lzma --enable-decklink --enable-zlib"
我是否错过了GIF的关键作品?
编辑2016年6月27日
感谢Gwen,我有第一个输出:我将context->pix_fmt
设置为AV_PIX_FMT_BGR8
。顺便说一下,我仍然面临生成的GIF的一些问题。它没有播放,编码似乎失败了。
使用ffmpeg(左)在命令行中生成GIF。 。 。 以编程方式生成GIF(右)
看起来某些选项未定义...也可能是QImage和AVFrame之间的错误转换?我更新了上面的代码。它代表了很多代码,所以我试图保持简短。不要犹豫,询问更多细节。
编辑结束
我对ffmpeg并不熟悉,任何形式的帮助都会受到高度赞赏。谢谢。
答案 0 :(得分:4)
GIF只能支持256色位图(每像素8位)。这可能是您遇到Specified pixel format rgb24 is invalid or not supported
错误的原因。
您需要使用的像素格式为AV_PIX_FMT_PAL8
(8 bit with RGB32 palette)。
答案 1 :(得分:2)
这是一种使用ffmpeg将QImage转换为GIF格式的方法。我试图尽可能清楚地删除错误。
首先,初始化ffmpeg:
AVOutputFormat * outputFormat = Q_NULLPTR;
AVFormatContext * formatContext = Q_NULLPTR;
avformat_alloc_output_context2(&formatContext, NULL, NULL, filePath.data()); // i.e. filePath="C:/Users/.../qt_temp.Jv7868.mp4"
// Adding the video streams using the default format codecs and initializing the codecs...
outputFormat = formatContext->oformat;
if (outputFormat->video_codec != AV_CODEC_ID_NONE) {
// Finding a registered encoder with a matching codec ID...
*codec = avcodec_find_encoder(outputFormat->video_codec);
// Adding a new stream to a media file...
stream = avformat_new_stream(formatContext, *codec);
stream->id = formatContext->nb_streams - 1;
AVCodecContext * codecContext = avcodec_alloc_context3(*codec);
switch ((*codec)->type) {
case AVMEDIA_TYPE_VIDEO:
codecContext->codec_id = outputFormat->video_codec; // here, outputFormat->video_codec should be AV_CODEC_ID_GIF
codecContext->bit_rate = 400000;
codecContext->width = 1240;
codecContext->height = 874;
codecContext->pix_fmt = AV_PIX_FMT_RGB8;
...
// Timebase: this is the fundamental unit of time (in seconds) in terms of which frame
// timestamps are represented. For fixed-fps content, timebase should be 1/framerate
// and timestamp increments should be identical to 1.
stream->time_base = (AVRational){1, fps}; // i.e. fps=1
codecContext->time_base = stream->time_base;
// Emit 1 intra frame every 12 frames at most
codecContext->gop_size = 12;
codecContext->pix_fmt = AV_PIX_FMT_YUV420P;
if (codecContext->codec_id == AV_CODEC_ID_H264) {
av_opt_set(codecContext->priv_data, "preset", "slow", 0);
}
break;
}
if (formatContext->oformat->flags & AVFMT_GLOBALHEADER) {
codecContext->flags |= CODEC_FLAG_GLOBAL_HEADER;
}
}
avcodec_open2(codecContext, codec, NULL);
// Here we need 3 frames. Basically, the QImage is firstly extracted in AV_PIX_FMT_BGRA.
// We need then to convert it to AV_PIX_FMT_RGB8 which is required by the .gif format.
// If we do that directly, there will be some artefacts and bad effects... to prevent that
// we convert FIRST AV_PIX_FMT_BGRA into AV_PIX_FMT_YUV420P THEN into AV_PIX_FMT_RGB8.
frame = allocPicture(codecContext->width, codecContext->height, codecContext->pix_fmt); // here, codecContext->pix_fmt should be AV_PIX_FMT_RGB8
tmpFrame = allocPicture(codecContext->width, codecContext->height, AV_PIX_FMT_BGRA);
yuvFrame = allocPicture(codecContext->width, codecContext->height, AV_PIX_FMT_YUV420P);
avcodec_parameters_from_context(stream->codecpar, codecContext);
av_dump_format(formatContext, 0, filePath.data(), 1);
if (!(outputFormat->flags & AVFMT_NOFILE)) {
avio_open(&formatContext->pb, filePath.data(), AVIO_FLAG_WRITE);
}
// Writing the stream header, if any...
avformat_write_header(formatContext, NULL);
然后是主要部分,添加一个QImage(例如从循环接收):
// -> parameter: QImage image
const qint32 width = image.width();
const qint32 height = image.height();
// When we pass a frame to the encoder, it may keep a reference to it internally;
// make sure we do not overwrite it here!
av_frame_make_writable(tmpFrame);
// Converting QImage to AV_PIX_FMT_BGRA AVFrame ...
for (qint32 y = 0; y < height(); y++) {
const uint8_t * scanline = image.scanLine(y);
for (qint32 x = 0; x < width() * 4; x++) {
tmpFrame->data[0][y * tmpFrame->linesize[0] + x] = scanline[x];
}
}
// Make sure to clear the frame. It prevents a bug that displays only the
// first captured frame on the GIF export.
if (frame) {
av_frame_free(&frame);
frame = Q_NULLPTR;
}
frame = allocPicture(codecContext->width, codecContext->height, codecContext->pix_fmt);
if (yuvFrame) {
av_frame_free(&yuvFrame);
yuvFrame = Q_NULLPTR;
}
yuvFrame = allocPicture(codecContext->width, codecContext->height, AV_PIX_FMT_YUV420P);
// Converting BGRA -> YUV420P...
if (!swsCtx) {
swsCtx = sws_getContext(width, height,
AV_PIX_FMT_BGRA,
width, height,
AV_PIX_FMT_YUV420P,
swsFlags, NULL, NULL, NULL);
}
// ...then converting YUV420P -> RGB8 (natif GIF format pixel)
if (!swsGIFCtx) {
swsGIFCtx = sws_getContext(width, height,
AV_PIX_FMT_YUV420P,
codecContext->width, codecContext->height,
codecContext->pix_fmt,
this->swsFlags, NULL, NULL, NULL);
}
// This double scaling prevent some artifacts on the GIF and improve
// significantly the display quality
sws_scale(swsCtx,
(const uint8_t * const *)tmpFrame->data,
tmpFrame->linesize,
0,
codecContext->height,
yuvFrame->data,
yuvFrame->linesize);
sws_scale(swsGIFCtx,
(const uint8_t * const *)yuvFrame->data,
yuvFrame->linesize,
0,
codecContext->height,
frame->data,
frame->linesize);
...
AVPacket packet;
int gotPacket = 0;
av_init_packet(&packet);
// Packet data will be allocated by the encoder
packet.data = NULL;
packet.size = 0;
frame->pts = nextPts++; // nextPts starts at 0
avcodec_encode_video2(codecContext, &packet, frame, &gotPacket);
if (gotPacket) {
// Rescale output packet timestamp values from codec to stream timebase
av_packet_rescale_ts(packet, *codecContext->time_base, stream->time_base);
packet->stream_index = stream->index;
// Write the compressed frame to the media file.
av_interleaved_write_frame(formatContext, packet);
av_packet_unref(&this->packet);
}
关闭ffmpeg:
// Retrieving delayed frames if any...
// Note: mainly used for video generation, it might be useless for .gif.
for (int gotOutput = 1; gotOutput;) {
avcodec_encode_video2(codecContext, &packet, NULL, &gotOutput);
if (gotOutput) {
// Rescale output packet timestamp values from codec to stream timebase
av_packet_rescale_ts(packet, *codecContext->time_base, stream->time_base);
packet->stream_index = stream->index;
// Write the compressed frame to the media file.
av_interleaved_write_frame(formatContext, packet);
av_packet_unref(&packet);
}
}
av_write_trailer(formatContext);
avcodec_free_context(&codecContext);
av_frame_free(&frame);
av_frame_free(&tmpFrame);
sws_freeContext(swsCtx);
if (!(outputFormat->flags & AVFMT_NOFILE)) {
// Closing the output file...
avio_closep(&formatContext->pb);
}
avformat_free_context(formatContext);
我不认为这是最简单的方法,但至少它对我有用。我让问题公开。请随时评论/改进/回答这个问题。
答案 2 :(得分:0)
我根据@Sierra的代码片段制作了一个更完善的c ++类。它不使用任何不推荐使用的ffmpeg函数,因此目前应该是面向未来的。该类分为三个部分:一个将GIF图像标头写入GIF文件的函数,一个在帧位置插入(可能已裁剪的)图像的函数,以及将帧提交到文件中,刷新和删除分配的ffmpeg结构。
关于该课程的几点注意事项:
GIF98a规范在有关图形控件扩展的部分中进行了说明
vii)延迟时间-如果不为0,则该字段指定在继续处理数据流之前要等待的百分之一(1/100)秒。渲染图形后,时钟立即开始滴答。此字段可以与“用户输入标志”字段一起使用。
这意味着对于100 FPS的图像速率,最小值为1(这将很难在大多数监视器上呈现...),对于50 FPS的图像速率,下一个最小值为2,而下一个为对于33.3 FPS的图像速率,该值为3。因此,完全不可能达到60 FPS。
换句话说,实际上以100 FPS保存了60 FPS GIF,以50 FPS保存了30 FPS,像OP试图将其用作16.6 FPS一样保存了15 FPS。
我已经注意到GIF中尺寸奇异的黄色瑕疵。因此,我的课程剪掉了宽度/长度的额外像素。
AV_PIX_FMT_BGR8框架为我工作,这就是我的课堂创造的。
最后一个也是最重要的一点,必须按顺序指定帧号,从1开始。尝试不按顺序添加帧将使类中止编码过程,直到下一个GIFExporter::commitFile()
调用为止。之所以这样实现是因为,我是从QOpenGLWidget帧缓冲区中抓取图像的,如果绘制了太多的对象,它们会滞后,从而导致某些帧丢失。
下面的Github要点代码:
<script src="https://gist.github.com/ZenulAbidin/4fb0047e459bd929a7c02784f78daca1.js"></script>