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_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 @@ bool FFmpegEncoderSetVideo(struct FFmpegEncoder* encoder, const char* vcodec, un
{ 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 FFmpegEncoderVerifyContainer(struct FFmpegEncoder* encoder) {
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 @@ bool FFmpegEncoderOpen(struct FFmpegEncoder* encoder, const char* 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,25 +278,27 @@ void FFmpegEncoderClose(struct FFmpegEncoder* encoder) {
av_write_trailer(encoder->context);
avio_close(encoder->context->pb);
av_free(encoder->postaudioBuffer);
if (encoder->audioBuffer) {
av_free(encoder->audioBuffer);
if (encoder->audioCodec) {
av_free(encoder->postaudioBuffer);
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);
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);
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) {
struct FFmpegEncoder* encoder = (struct FFmpegEncoder*) stream;
if (!encoder->context) {
if (!encoder->context || !encoder->audioCodec) {
return;
}

View File

@ -167,6 +167,16 @@ VideoView::VideoView(QWidget* parent)
.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::setFilename(const QString& fname) {
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 @@ void VideoView::setVideoCodec(const QString& codec, bool manual) {
if (!FFmpegEncoderSetVideo(&m_encoder, m_videoCodecCstr, m_vbr)) {
free(m_videoCodecCstr);
m_videoCodecCstr = nullptr;
m_videoCodec = QString();
}
validateSettings();
if (manual) {
@ -251,6 +267,7 @@ void VideoView::setContainer(const QString& container, bool manual) {
if (!FFmpegEncoderSetContainer(&m_encoder, m_containerCstr)) {
free(m_containerCstr);
m_containerCstr = nullptr;
m_container = QString();
}
validateSettings();
if (manual) {
@ -316,21 +333,21 @@ void VideoView::showAdvanced(bool show) {
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 {

View File

@ -146,6 +146,16 @@
</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>