diff --git a/src/platform/ffmpeg/ffmpeg-encoder.c b/src/platform/ffmpeg/ffmpeg-encoder.c index b4d33e462..65925e986 100644 --- a/src/platform/ffmpeg/ffmpeg-encoder.c +++ b/src/platform/ffmpeg/ffmpeg-encoder.c @@ -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; } diff --git a/src/platform/qt/VideoView.cpp b/src/platform/qt/VideoView.cpp index 0f36b6d58..e996ed005 100644 --- a/src/platform/qt/VideoView.cpp +++ b/src/platform/qt/VideoView.cpp @@ -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 { diff --git a/src/platform/qt/VideoView.ui b/src/platform/qt/VideoView.ui index 98bfb4034..f66889b22 100644 --- a/src/platform/qt/VideoView.ui +++ b/src/platform/qt/VideoView.ui @@ -146,6 +146,16 @@ + + + + GIF + + + presets + + +