Video: Allow GIF recording

This commit is contained in:
Jeffrey Pfau 2014-11-18 01:40:48 -08:00
parent 5a6d09405d
commit 5b5c8c8d2d
3 changed files with 98 additions and 59 deletions

View File

@ -49,6 +49,12 @@ bool FFmpegEncoderSetAudio(struct FFmpegEncoder* encoder, const char* acodec, un
{ AV_SAMPLE_FMT_DBL, 4 }, { AV_SAMPLE_FMT_DBL, 4 },
{ AV_SAMPLE_FMT_DBLP, 4 } { AV_SAMPLE_FMT_DBLP, 4 }
}; };
if (!acodec) {
encoder->audioCodec = 0;
return true;
}
AVCodec* codec = avcodec_find_encoder_by_name(acodec); AVCodec* codec = avcodec_find_encoder_by_name(acodec);
if (!codec) { if (!codec) {
return false; return false;
@ -106,6 +112,8 @@ bool FFmpegEncoderSetVideo(struct FFmpegEncoder* encoder, const char* vcodec, un
{ AV_PIX_FMT_RGB0, 3 }, { AV_PIX_FMT_RGB0, 3 },
{ AV_PIX_FMT_0BGR, 3 }, { AV_PIX_FMT_0BGR, 3 },
{ AV_PIX_FMT_0RGB, 3 }, { AV_PIX_FMT_0RGB, 3 },
{ AV_PIX_FMT_RGB8, 3 },
{ AV_PIX_FMT_BGR8, 3 },
{ AV_PIX_FMT_YUV422P, 4 }, { AV_PIX_FMT_YUV422P, 4 },
{ AV_PIX_FMT_YUV444P, 5 }, { AV_PIX_FMT_YUV444P, 5 },
{ AV_PIX_FMT_YUV420P, 6 } { AV_PIX_FMT_YUV420P, 6 }
@ -153,10 +161,10 @@ bool FFmpegEncoderVerifyContainer(struct FFmpegEncoder* encoder) {
AVOutputFormat* oformat = av_guess_format(encoder->containerFormat, 0, 0); AVOutputFormat* oformat = av_guess_format(encoder->containerFormat, 0, 0);
AVCodec* acodec = avcodec_find_encoder_by_name(encoder->audioCodec); AVCodec* acodec = avcodec_find_encoder_by_name(encoder->audioCodec);
AVCodec* vcodec = avcodec_find_encoder_by_name(encoder->videoCodec); AVCodec* vcodec = avcodec_find_encoder_by_name(encoder->videoCodec);
if (!acodec || !vcodec || !oformat) { if ((encoder->audioCodec && !acodec) || !vcodec || !oformat) {
return false; 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; return false;
} }
if (!avformat_query_codec(oformat, vcodec->id, FF_COMPLIANCE_EXPERIMENTAL)) { if (!avformat_query_codec(oformat, vcodec->id, FF_COMPLIANCE_EXPERIMENTAL)) {
@ -168,7 +176,7 @@ bool FFmpegEncoderVerifyContainer(struct FFmpegEncoder* encoder) {
bool FFmpegEncoderOpen(struct FFmpegEncoder* encoder, const char* outfile) { bool FFmpegEncoderOpen(struct FFmpegEncoder* encoder, const char* outfile) {
AVCodec* acodec = avcodec_find_encoder_by_name(encoder->audioCodec); AVCodec* acodec = avcodec_find_encoder_by_name(encoder->audioCodec);
AVCodec* vcodec = avcodec_find_encoder_by_name(encoder->videoCodec); AVCodec* vcodec = avcodec_find_encoder_by_name(encoder->videoCodec);
if (!acodec || !vcodec || !FFmpegEncoderVerifyContainer(encoder)) { if ((encoder->audioCodec && !acodec) || !vcodec || !FFmpegEncoderVerifyContainer(encoder)) {
return false; return false;
} }
@ -181,44 +189,46 @@ bool FFmpegEncoderOpen(struct FFmpegEncoder* encoder, const char* outfile) {
encoder->context->oformat = av_guess_format(encoder->containerFormat, 0, 0); encoder->context->oformat = av_guess_format(encoder->containerFormat, 0, 0);
encoder->audioStream = avformat_new_stream(encoder->context, acodec); if (acodec) {
encoder->audio = encoder->audioStream->codec; encoder->audioStream = avformat_new_stream(encoder->context, acodec);
encoder->audio->bit_rate = encoder->audioBitrate; encoder->audio = encoder->audioStream->codec;
encoder->audio->channels = 2; encoder->audio->bit_rate = encoder->audioBitrate;
encoder->audio->channel_layout = AV_CH_LAYOUT_STEREO; encoder->audio->channels = 2;
encoder->audio->sample_rate = encoder->sampleRate; encoder->audio->channel_layout = AV_CH_LAYOUT_STEREO;
encoder->audio->sample_fmt = encoder->sampleFormat; encoder->audio->sample_rate = encoder->sampleRate;
AVDictionary* opts = 0; encoder->audio->sample_fmt = encoder->sampleFormat;
av_dict_set(&opts, "strict", "-2", 0); AVDictionary* opts = 0;
if (encoder->context->oformat->flags & AVFMT_GLOBALHEADER) { av_dict_set(&opts, "strict", "-2", 0);
encoder->audio->flags |= CODEC_FLAG_GLOBAL_HEADER; if (encoder->context->oformat->flags & AVFMT_GLOBALHEADER) {
} encoder->audio->flags |= CODEC_FLAG_GLOBAL_HEADER;
avcodec_open2(encoder->audio, acodec, &opts); }
av_dict_free(&opts); avcodec_open2(encoder->audio, acodec, &opts);
encoder->audioFrame = av_frame_alloc(); av_dict_free(&opts);
encoder->audioFrame->nb_samples = encoder->audio->frame_size; encoder->audioFrame = av_frame_alloc();
encoder->audioFrame->format = encoder->audio->sample_fmt; encoder->audioFrame->nb_samples = encoder->audio->frame_size;
encoder->audioFrame->pts = 0; encoder->audioFrame->format = encoder->audio->sample_fmt;
encoder->resampleContext = avresample_alloc_context(); encoder->audioFrame->pts = 0;
av_opt_set_int(encoder->resampleContext, "in_channel_layout", AV_CH_LAYOUT_STEREO, 0); encoder->resampleContext = avresample_alloc_context();
av_opt_set_int(encoder->resampleContext, "out_channel_layout", AV_CH_LAYOUT_STEREO, 0); av_opt_set_int(encoder->resampleContext, "in_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_channel_layout", AV_CH_LAYOUT_STEREO, 0);
av_opt_set_int(encoder->resampleContext, "out_sample_rate", encoder->sampleRate, 0); av_opt_set_int(encoder->resampleContext, "in_sample_rate", PREFERRED_SAMPLE_RATE, 0);
av_opt_set_int(encoder->resampleContext, "in_sample_fmt", AV_SAMPLE_FMT_S16, 0); av_opt_set_int(encoder->resampleContext, "out_sample_rate", encoder->sampleRate, 0);
av_opt_set_int(encoder->resampleContext, "out_sample_fmt", encoder->sampleFormat, 0); av_opt_set_int(encoder->resampleContext, "in_sample_fmt", AV_SAMPLE_FMT_S16, 0);
avresample_open(encoder->resampleContext); av_opt_set_int(encoder->resampleContext, "out_sample_fmt", encoder->sampleFormat, 0);
encoder->audioBufferSize = (encoder->audioFrame->nb_samples * PREFERRED_SAMPLE_RATE / encoder->sampleRate) * 4; avresample_open(encoder->resampleContext);
encoder->audioBuffer = av_malloc(encoder->audioBufferSize); encoder->audioBufferSize = (encoder->audioFrame->nb_samples * PREFERRED_SAMPLE_RATE / encoder->sampleRate) * 4;
encoder->postaudioBufferSize = av_samples_get_buffer_size(0, encoder->audio->channels, encoder->audio->frame_size, encoder->audio->sample_fmt, 0); encoder->audioBuffer = av_malloc(encoder->audioBufferSize);
encoder->postaudioBuffer = av_malloc(encoder->postaudioBufferSize); encoder->postaudioBufferSize = av_samples_get_buffer_size(0, encoder->audio->channels, encoder->audio->frame_size, encoder->audio->sample_fmt, 0);
avcodec_fill_audio_frame(encoder->audioFrame, encoder->audio->channels, encoder->audio->sample_fmt, (const uint8_t*) encoder->postaudioBuffer, encoder->postaudioBufferSize, 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 && if (encoder->audio->codec->id == AV_CODEC_ID_AAC &&
(strcasecmp(encoder->containerFormat, "mp4") || (strcasecmp(encoder->containerFormat, "mp4") ||
strcasecmp(encoder->containerFormat, "m4v") || strcasecmp(encoder->containerFormat, "m4v") ||
strcasecmp(encoder->containerFormat, "mov"))) { strcasecmp(encoder->containerFormat, "mov"))) {
// MP4 container doesn't support the raw ADTS AAC format that the encoder spits out // MP4 container doesn't support the raw ADTS AAC format that the encoder spits out
encoder->absf = av_bitstream_filter_init("aac_adtstoasc"); encoder->absf = av_bitstream_filter_init("aac_adtstoasc");
}
} }
encoder->videoStream = avformat_new_stream(encoder->context, vcodec); encoder->videoStream = avformat_new_stream(encoder->context, vcodec);
@ -268,25 +278,27 @@ void FFmpegEncoderClose(struct FFmpegEncoder* encoder) {
av_write_trailer(encoder->context); av_write_trailer(encoder->context);
avio_close(encoder->context->pb); avio_close(encoder->context->pb);
av_free(encoder->postaudioBuffer); if (encoder->audioCodec) {
if (encoder->audioBuffer) { av_free(encoder->postaudioBuffer);
av_free(encoder->audioBuffer); if (encoder->audioBuffer) {
av_free(encoder->audioBuffer);
}
av_frame_free(&encoder->audioFrame);
avcodec_close(encoder->audio);
if (encoder->resampleContext) {
avresample_close(encoder->resampleContext);
}
if (encoder->absf) {
av_bitstream_filter_close(encoder->absf);
encoder->absf = 0;
}
} }
av_frame_free(&encoder->audioFrame);
avcodec_close(encoder->audio);
av_frame_free(&encoder->videoFrame); av_frame_free(&encoder->videoFrame);
avcodec_close(encoder->video); avcodec_close(encoder->video);
if (encoder->resampleContext) {
avresample_close(encoder->resampleContext);
}
if (encoder->absf) {
av_bitstream_filter_close(encoder->absf);
encoder->absf = 0;
}
sws_freeContext(encoder->scaleContext); sws_freeContext(encoder->scaleContext);
avformat_free_context(encoder->context); avformat_free_context(encoder->context);
@ -299,7 +311,7 @@ bool FFmpegEncoderIsOpen(struct FFmpegEncoder* encoder) {
void _ffmpegPostAudioFrame(struct GBAAVStream* stream, int32_t left, int32_t right) { void _ffmpegPostAudioFrame(struct GBAAVStream* stream, int32_t left, int32_t right) {
struct FFmpegEncoder* encoder = (struct FFmpegEncoder*) stream; struct FFmpegEncoder* encoder = (struct FFmpegEncoder*) stream;
if (!encoder->context) { if (!encoder->context || !encoder->audioCodec) {
return; return;
} }

View File

@ -167,6 +167,16 @@ VideoView::VideoView(QWidget* parent)
.height = 160, .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()); setAudioCodec(m_ui.audio->currentText());
setVideoCodec(m_ui.video->currentText()); setVideoCodec(m_ui.video->currentText());
setAudioBitrate(m_ui.abr->value()); setAudioBitrate(m_ui.abr->value());
@ -219,10 +229,15 @@ void VideoView::setFilename(const QString& fname) {
void VideoView::setAudioCodec(const QString& codec, bool manual) { void VideoView::setAudioCodec(const QString& codec, bool manual) {
free(m_audioCodecCstr); free(m_audioCodecCstr);
m_audioCodec = sanitizeCodec(codec, s_acodecMap); 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)) { if (!FFmpegEncoderSetAudio(&m_encoder, m_audioCodecCstr, m_abr)) {
free(m_audioCodecCstr); free(m_audioCodecCstr);
m_audioCodecCstr = nullptr; m_audioCodecCstr = nullptr;
m_audioCodec = QString();
} }
validateSettings(); validateSettings();
if (manual) { if (manual) {
@ -237,6 +252,7 @@ void VideoView::setVideoCodec(const QString& codec, bool manual) {
if (!FFmpegEncoderSetVideo(&m_encoder, m_videoCodecCstr, m_vbr)) { if (!FFmpegEncoderSetVideo(&m_encoder, m_videoCodecCstr, m_vbr)) {
free(m_videoCodecCstr); free(m_videoCodecCstr);
m_videoCodecCstr = nullptr; m_videoCodecCstr = nullptr;
m_videoCodec = QString();
} }
validateSettings(); validateSettings();
if (manual) { if (manual) {
@ -251,6 +267,7 @@ void VideoView::setContainer(const QString& container, bool manual) {
if (!FFmpegEncoderSetContainer(&m_encoder, m_containerCstr)) { if (!FFmpegEncoderSetContainer(&m_encoder, m_containerCstr)) {
free(m_containerCstr); free(m_containerCstr);
m_containerCstr = nullptr; m_containerCstr = nullptr;
m_container = QString();
} }
validateSettings(); validateSettings();
if (manual) { if (manual) {
@ -316,21 +333,21 @@ void VideoView::showAdvanced(bool show) {
bool VideoView::validateSettings() { bool VideoView::validateSettings() {
bool valid = !m_filename.isNull() && !FFmpegEncoderIsOpen(&m_encoder); bool valid = !m_filename.isNull() && !FFmpegEncoderIsOpen(&m_encoder);
if (!m_audioCodecCstr) { if (m_audioCodec.isNull()) {
valid = false; valid = false;
m_ui.audio->setStyleSheet("QComboBox { color: red; }"); m_ui.audio->setStyleSheet("QComboBox { color: red; }");
} else { } else {
m_ui.audio->setStyleSheet(""); m_ui.audio->setStyleSheet("");
} }
if (!m_videoCodecCstr) { if (m_videoCodec.isNull()) {
valid = false; valid = false;
m_ui.video->setStyleSheet("QComboBox { color: red; }"); m_ui.video->setStyleSheet("QComboBox { color: red; }");
} else { } else {
m_ui.video->setStyleSheet(""); m_ui.video->setStyleSheet("");
} }
if (!m_containerCstr) { if (m_container.isNull()) {
valid = false; valid = false;
m_ui.container->setStyleSheet("QComboBox { color: red; }"); m_ui.container->setStyleSheet("QComboBox { color: red; }");
} else { } else {

View File

@ -146,6 +146,16 @@
</attribute> </attribute>
</widget> </widget>
</item> </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> </layout>
</item> </item>
<item> <item>