mirror of https://github.com/mgba-emu/mgba.git
Video: Allow GIF recording
This commit is contained in:
parent
5a6d09405d
commit
5b5c8c8d2d
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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>
|
||||
|
|
Loading…
Reference in New Issue