Video: Allow GIF recording
Jeffrey Pfau jeffrey@endrift.com
Tue, 18 Nov 2014 01:40:48 -0800
3 files changed,
98 insertions(+),
59 deletions(-)
M
src/platform/ffmpeg/ffmpeg-encoder.c
→
src/platform/ffmpeg/ffmpeg-encoder.c
@@ -49,6 +49,12 @@ { AV_SAMPLE_FMT_FLTP, 3 },
{ AV_SAMPLE_FMT_DBL, 4 }, { AV_SAMPLE_FMT_DBLP, 4 } }; + + if (!acodec) { + encoder->audioCodec = 0; + return true; + } + AVCodec* codec = avcodec_find_encoder_by_name(acodec); if (!codec) { return false;@@ -106,6 +112,8 @@ { AV_PIX_FMT_BGR0, 3 },
{ AV_PIX_FMT_RGB0, 3 }, { AV_PIX_FMT_0BGR, 3 }, { AV_PIX_FMT_0RGB, 3 }, + { AV_PIX_FMT_RGB8, 3 }, + { AV_PIX_FMT_BGR8, 3 }, { AV_PIX_FMT_YUV422P, 4 }, { AV_PIX_FMT_YUV444P, 5 }, { AV_PIX_FMT_YUV420P, 6 }@@ -153,10 +161,10 @@ bool FFmpegEncoderVerifyContainer(struct FFmpegEncoder* encoder) {
AVOutputFormat* oformat = av_guess_format(encoder->containerFormat, 0, 0); AVCodec* acodec = avcodec_find_encoder_by_name(encoder->audioCodec); AVCodec* vcodec = avcodec_find_encoder_by_name(encoder->videoCodec); - if (!acodec || !vcodec || !oformat) { + if ((encoder->audioCodec && !acodec) || !vcodec || !oformat) { return false; } - if (!avformat_query_codec(oformat, acodec->id, FF_COMPLIANCE_EXPERIMENTAL)) { + if (encoder->audioCodec && !avformat_query_codec(oformat, acodec->id, FF_COMPLIANCE_EXPERIMENTAL)) { return false; } if (!avformat_query_codec(oformat, vcodec->id, FF_COMPLIANCE_EXPERIMENTAL)) {@@ -168,7 +176,7 @@
bool FFmpegEncoderOpen(struct FFmpegEncoder* encoder, const char* outfile) { AVCodec* acodec = avcodec_find_encoder_by_name(encoder->audioCodec); AVCodec* vcodec = avcodec_find_encoder_by_name(encoder->videoCodec); - if (!acodec || !vcodec || !FFmpegEncoderVerifyContainer(encoder)) { + if ((encoder->audioCodec && !acodec) || !vcodec || !FFmpegEncoderVerifyContainer(encoder)) { return false; }@@ -181,44 +189,46 @@ avformat_alloc_output_context2(&encoder->context, 0, 0, outfile);
encoder->context->oformat = av_guess_format(encoder->containerFormat, 0, 0); - encoder->audioStream = avformat_new_stream(encoder->context, acodec); - encoder->audio = encoder->audioStream->codec; - encoder->audio->bit_rate = encoder->audioBitrate; - encoder->audio->channels = 2; - encoder->audio->channel_layout = AV_CH_LAYOUT_STEREO; - encoder->audio->sample_rate = encoder->sampleRate; - encoder->audio->sample_fmt = encoder->sampleFormat; - AVDictionary* opts = 0; - av_dict_set(&opts, "strict", "-2", 0); - if (encoder->context->oformat->flags & AVFMT_GLOBALHEADER) { - encoder->audio->flags |= CODEC_FLAG_GLOBAL_HEADER; - } - avcodec_open2(encoder->audio, acodec, &opts); - av_dict_free(&opts); - encoder->audioFrame = av_frame_alloc(); - encoder->audioFrame->nb_samples = encoder->audio->frame_size; - encoder->audioFrame->format = encoder->audio->sample_fmt; - encoder->audioFrame->pts = 0; - encoder->resampleContext = avresample_alloc_context(); - av_opt_set_int(encoder->resampleContext, "in_channel_layout", AV_CH_LAYOUT_STEREO, 0); - av_opt_set_int(encoder->resampleContext, "out_channel_layout", AV_CH_LAYOUT_STEREO, 0); - av_opt_set_int(encoder->resampleContext, "in_sample_rate", PREFERRED_SAMPLE_RATE, 0); - av_opt_set_int(encoder->resampleContext, "out_sample_rate", encoder->sampleRate, 0); - av_opt_set_int(encoder->resampleContext, "in_sample_fmt", AV_SAMPLE_FMT_S16, 0); - av_opt_set_int(encoder->resampleContext, "out_sample_fmt", encoder->sampleFormat, 0); - avresample_open(encoder->resampleContext); - encoder->audioBufferSize = (encoder->audioFrame->nb_samples * PREFERRED_SAMPLE_RATE / encoder->sampleRate) * 4; - encoder->audioBuffer = av_malloc(encoder->audioBufferSize); - encoder->postaudioBufferSize = av_samples_get_buffer_size(0, encoder->audio->channels, encoder->audio->frame_size, encoder->audio->sample_fmt, 0); - encoder->postaudioBuffer = av_malloc(encoder->postaudioBufferSize); - avcodec_fill_audio_frame(encoder->audioFrame, encoder->audio->channels, encoder->audio->sample_fmt, (const uint8_t*) encoder->postaudioBuffer, encoder->postaudioBufferSize, 0); + if (acodec) { + encoder->audioStream = avformat_new_stream(encoder->context, acodec); + encoder->audio = encoder->audioStream->codec; + encoder->audio->bit_rate = encoder->audioBitrate; + encoder->audio->channels = 2; + encoder->audio->channel_layout = AV_CH_LAYOUT_STEREO; + encoder->audio->sample_rate = encoder->sampleRate; + encoder->audio->sample_fmt = encoder->sampleFormat; + AVDictionary* opts = 0; + av_dict_set(&opts, "strict", "-2", 0); + if (encoder->context->oformat->flags & AVFMT_GLOBALHEADER) { + encoder->audio->flags |= CODEC_FLAG_GLOBAL_HEADER; + } + avcodec_open2(encoder->audio, acodec, &opts); + av_dict_free(&opts); + encoder->audioFrame = av_frame_alloc(); + encoder->audioFrame->nb_samples = encoder->audio->frame_size; + encoder->audioFrame->format = encoder->audio->sample_fmt; + encoder->audioFrame->pts = 0; + encoder->resampleContext = avresample_alloc_context(); + av_opt_set_int(encoder->resampleContext, "in_channel_layout", AV_CH_LAYOUT_STEREO, 0); + av_opt_set_int(encoder->resampleContext, "out_channel_layout", AV_CH_LAYOUT_STEREO, 0); + av_opt_set_int(encoder->resampleContext, "in_sample_rate", PREFERRED_SAMPLE_RATE, 0); + av_opt_set_int(encoder->resampleContext, "out_sample_rate", encoder->sampleRate, 0); + av_opt_set_int(encoder->resampleContext, "in_sample_fmt", AV_SAMPLE_FMT_S16, 0); + av_opt_set_int(encoder->resampleContext, "out_sample_fmt", encoder->sampleFormat, 0); + avresample_open(encoder->resampleContext); + encoder->audioBufferSize = (encoder->audioFrame->nb_samples * PREFERRED_SAMPLE_RATE / encoder->sampleRate) * 4; + encoder->audioBuffer = av_malloc(encoder->audioBufferSize); + encoder->postaudioBufferSize = av_samples_get_buffer_size(0, encoder->audio->channels, encoder->audio->frame_size, encoder->audio->sample_fmt, 0); + encoder->postaudioBuffer = av_malloc(encoder->postaudioBufferSize); + avcodec_fill_audio_frame(encoder->audioFrame, encoder->audio->channels, encoder->audio->sample_fmt, (const uint8_t*) encoder->postaudioBuffer, encoder->postaudioBufferSize, 0); - if (encoder->audio->codec->id == AV_CODEC_ID_AAC && - (strcasecmp(encoder->containerFormat, "mp4") || - strcasecmp(encoder->containerFormat, "m4v") || - strcasecmp(encoder->containerFormat, "mov"))) { - // MP4 container doesn't support the raw ADTS AAC format that the encoder spits out - encoder->absf = av_bitstream_filter_init("aac_adtstoasc"); + if (encoder->audio->codec->id == AV_CODEC_ID_AAC && + (strcasecmp(encoder->containerFormat, "mp4") || + strcasecmp(encoder->containerFormat, "m4v") || + strcasecmp(encoder->containerFormat, "mov"))) { + // MP4 container doesn't support the raw ADTS AAC format that the encoder spits out + encoder->absf = av_bitstream_filter_init("aac_adtstoasc"); + } } encoder->videoStream = avformat_new_stream(encoder->context, vcodec);@@ -268,24 +278,26 @@ }
av_write_trailer(encoder->context); avio_close(encoder->context->pb); - av_free(encoder->postaudioBuffer); - if (encoder->audioBuffer) { - av_free(encoder->audioBuffer); - } - av_frame_free(&encoder->audioFrame); - avcodec_close(encoder->audio); + if (encoder->audioCodec) { + av_free(encoder->postaudioBuffer); + if (encoder->audioBuffer) { + av_free(encoder->audioBuffer); + } + av_frame_free(&encoder->audioFrame); + avcodec_close(encoder->audio); - av_frame_free(&encoder->videoFrame); - avcodec_close(encoder->video); + if (encoder->resampleContext) { + avresample_close(encoder->resampleContext); + } - if (encoder->resampleContext) { - avresample_close(encoder->resampleContext); + if (encoder->absf) { + av_bitstream_filter_close(encoder->absf); + encoder->absf = 0; + } } - if (encoder->absf) { - av_bitstream_filter_close(encoder->absf); - encoder->absf = 0; - } + av_frame_free(&encoder->videoFrame); + avcodec_close(encoder->video); sws_freeContext(encoder->scaleContext);@@ -299,7 +311,7 @@ }
void _ffmpegPostAudioFrame(struct GBAAVStream* stream, int32_t left, int32_t right) { struct FFmpegEncoder* encoder = (struct FFmpegEncoder*) stream; - if (!encoder->context) { + if (!encoder->context || !encoder->audioCodec) { return; }
M
src/platform/qt/VideoView.cpp
→
src/platform/qt/VideoView.cpp
@@ -167,6 +167,16 @@ .width = 240,
.height = 160, }); + addPreset(m_ui.presetGIF, (Preset) { + .container = "GIF", + .vcodec = "GIF", + .acodec = "None", + .vbr = 0, + .abr = 0, + .width = 240, + .height = 160, + }); + setAudioCodec(m_ui.audio->currentText()); setVideoCodec(m_ui.video->currentText()); setAudioBitrate(m_ui.abr->value());@@ -219,10 +229,15 @@
void VideoView::setAudioCodec(const QString& codec, bool manual) { free(m_audioCodecCstr); m_audioCodec = sanitizeCodec(codec, s_acodecMap); - m_audioCodecCstr = strdup(m_audioCodec.toLocal8Bit().constData()); + if (m_audioCodec == "none") { + m_audioCodecCstr = nullptr; + } else { + m_audioCodecCstr = strdup(m_audioCodec.toLocal8Bit().constData()); + } if (!FFmpegEncoderSetAudio(&m_encoder, m_audioCodecCstr, m_abr)) { free(m_audioCodecCstr); m_audioCodecCstr = nullptr; + m_audioCodec = QString(); } validateSettings(); if (manual) {@@ -237,6 +252,7 @@ m_videoCodecCstr = strdup(m_videoCodec.toLocal8Bit().constData());
if (!FFmpegEncoderSetVideo(&m_encoder, m_videoCodecCstr, m_vbr)) { free(m_videoCodecCstr); m_videoCodecCstr = nullptr; + m_videoCodec = QString(); } validateSettings(); if (manual) {@@ -251,6 +267,7 @@ m_containerCstr = strdup(m_container.toLocal8Bit().constData());
if (!FFmpegEncoderSetContainer(&m_encoder, m_containerCstr)) { free(m_containerCstr); m_containerCstr = nullptr; + m_container = QString(); } validateSettings(); if (manual) {@@ -316,21 +333,21 @@ }
bool VideoView::validateSettings() { bool valid = !m_filename.isNull() && !FFmpegEncoderIsOpen(&m_encoder); - if (!m_audioCodecCstr) { + if (m_audioCodec.isNull()) { valid = false; m_ui.audio->setStyleSheet("QComboBox { color: red; }"); } else { m_ui.audio->setStyleSheet(""); } - if (!m_videoCodecCstr) { + if (m_videoCodec.isNull()) { valid = false; m_ui.video->setStyleSheet("QComboBox { color: red; }"); } else { m_ui.video->setStyleSheet(""); } - if (!m_containerCstr) { + if (m_container.isNull()) { valid = false; m_ui.container->setStyleSheet("QComboBox { color: red; }"); } else {
M
src/platform/qt/VideoView.ui
→
src/platform/qt/VideoView.ui
@@ -146,6 +146,16 @@ <string notr="true">presets</string>
</attribute> </widget> </item> + <item> + <widget class="QRadioButton" name="presetGIF"> + <property name="text"> + <string>GIF</string> + </property> + <attribute name="buttonGroup"> + <string notr="true">presets</string> + </attribute> + </widget> + </item> </layout> </item> <item>