Skip to content

Commit

Permalink
Support for configuring audio codec and video framerate (ammen99#177)
Browse files Browse the repository at this point in the history
* Add rough support for changing audio codecs

* Updated README.md

* Changed audio codec option to -C

* Added rough implementation of audio codec, sample format, sample rate, and frame rate

* Rewrote help documentation to reflect new options

* Added fallback onto auto fmt, miscillanious fixes, broke something to be debugged soon

* Fixed thing that gave confusing syntax error?

* Fixed error output when user puts in invalid format; removed colon from format messages

* Fixed typo in perenthesis; fixed some wildly inconsistent whitespace

* Removed some commented out code

* When given an invalid format, wf-recorder errors out instead of choosing alternative. Also made log output more descriptive when this happens

* Finally got meson configuration options to work correctly

* Made man page congruent with --help

* Removed my references to default values, because the default values can be configured

* Made default sample format use configured default sample format

* Renamed DEFAULT_SAMPLE_FMT to FALLBACK_SAMPLE_FMT to reflect true purpose

* Also give chosen sample format when it is automatically chosen

* Copied the method get_codec_auto_sample_fmt appears to use to still work if it is not defined which sample formats a codec supports

* Made logging of the sample format used when user specified more similar to the logging of the sample format automatically chosen

* "video pixel format" -> "output video pixel format" in documentation

* Code style and rebase fixes

* Make audio options clearer to understand (acodec -> audio_codec)

* Add missing --sample-format (-X) from help function

* Change default sample rate to 48000

* Update manpage

* Move duplicate code to function

Co-authored-by: happysmash27 <[email protected]>
  • Loading branch information
llyyr and happysmash27 authored Jul 8, 2022
1 parent b09c86f commit bf161b4
Show file tree
Hide file tree
Showing 10 changed files with 194 additions and 54 deletions.
4 changes: 3 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,9 @@ You can record screen and sound simultaneously with
wf-recorder --audio --file=recording_with_audio.mp4
```

To specify a codec, use the `-c <codec>` option. To modify codec parameters, use `-p <option_name>=<option_value>`.
To specify a video codec, use the `-c <codec>` option. To modify codec parameters, use `-p <option_name>=<option_value>`.

You can also specify an audio codec, using `-C <codec>`. Alternatively, the long form `--audio-codec` can be used.

To set a specific output format, use the `--muxer` option. For example, to output to a video4linux2 loopback you might use:
```
Expand Down
4 changes: 4 additions & 0 deletions config.h.in
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
#pragma once

#define DEFAULT_CODEC "@default_codec@"
#define DEFAULT_FRAMERATE @default_framerate@
#define DEFAULT_AUDIO_CODEC "@default_audio_codec@"
#define DEFAULT_AUDIO_SAMPLE_RATE @default_audio_sample_rate@
#define FALLBACK_AUDIO_SAMPLE_FMT "@fallback_audio_sample_fmt@"
#mesondefine HAVE_PULSE
#mesondefine HAVE_OPENCL
#mesondefine HAVE_LIBAVDEVICE
28 changes: 27 additions & 1 deletion manpage/wf-recorder.1
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
.Dd $Mdocdate: December 31 2020 $
.Dd $Mdocdate: July 07 2022 $
.Dt WF-RECORDER 1
.Os
.Sh NAME
Expand All @@ -10,6 +10,7 @@
.Op Fl a , -audio Op Ar DEVICE
.Op Fl b, -bframes Ar max_b_frames
.Op Fl c, -codec Ar output_codec
.Op Fl r, -framerate Ar framerate
.Op Fl d, -device Ar encoding_device
.Op Fl D, -no-damage
.Op Fl f Ar filename.ext
Expand All @@ -23,6 +24,10 @@
.Op Fl t, -force-yuv
.Op Fl v, -version
.Op Fl x, -pixel-format
.Op Fl C, -audio-codec Ar output_audio_codec
.Op Fl P, -audio-codec-param Op Ar option_param=option_value
.Op Fl R, -sample-rate Ar sample_rate
.Op Fl X, -sample-format Ar sample_format
.Sh DESCRIPTION
.Nm
is a tool built to record your screen on Wayland compositors.
Expand Down Expand Up @@ -66,6 +71,10 @@ To modify codec parameters, use
.Fl p
.Ar option_name=option_value
.Pp
.It Fl r , -framerate
.Ar framerate
Changes an approximation of the video framerate. The default is 60.
.Pp
.It Fl d , -device Ar encoding_device
Selects the device to use when encoding the video.
.Pp
Expand Down Expand Up @@ -132,6 +141,23 @@ Set the output pixel format.
.Pp
List available formats using
.Dl $ ffmpeg -pix_fmts
.Pp
.It Fl C , -audio-codec
.Ar output_audio_codec
Specifies the codec of the audio.
.Pp
.It Fl P , -audio-codec-param Op Ar option_name=option_value
Change the audio codec parameters.
.Pp
.It Fl R , -sample-rate
.Ar sample_rate
Changes the audio sample rate, in HZ. The default value is 48000.
.Pp
.It Fl X , -sample-format Ar sample_format Set the output audio sample format.
.Pp
List available formats using
.Dl $ ffmpeg -sample_fmts

.El
.Sh EXAMPLES
To select a specific part of the screen you can either use
Expand Down
4 changes: 4 additions & 0 deletions meson.build
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,10 @@ project(
conf_data = configuration_data()

conf_data.set('default_codec', get_option('default_codec'))
conf_data.set('default_framerate', get_option('default_framerate'))
conf_data.set('default_audio_codec', get_option('default_audio_codec'))
conf_data.set('default_audio_sample_rate', get_option('default_audio_sample_rate'))
conf_data.set('fallback_audio_sample_fmt', get_option('fallback_audio_sample_fmt'))

version = '"@0@"'.format(meson.project_version())
git = find_program('git', native: true, required: false)
Expand Down
4 changes: 4 additions & 0 deletions meson_options.txt
Original file line number Diff line number Diff line change
@@ -1,2 +1,6 @@
option('default_codec', type: 'string', value: 'libx264', description: 'Codec that will be used by default')
option('default_framerate', type: 'integer', value: 60, description: 'Video framerate that will be used by default')
option('default_audio_codec', type: 'string', value: 'aac', description: 'Audio codec that will be used by default')
option('default_audio_sample_rate', type: 'integer', value: 48000, description: 'Audio sample rate that will be used by default')
option('fallback_audio_sample_fmt', type: 'string', value: 's16', description: 'Fallback audio sample format that will be used if wf-recorder cannot determine the sample formats supported by a codec')
option('pulse', type: 'feature', value: 'auto', description: 'Enable Pulseaudio')
75 changes: 62 additions & 13 deletions src/frame-writer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@


static const AVRational US_RATIONAL{1,1000000} ;
#define AUDIO_RATE 44100

// av_register_all was deprecated in 58.9.100, removed in 59.0.100
#if LIBAVCODEC_VERSION_INT < AV_VERSION_INT(59, 0, 100)
Expand Down Expand Up @@ -68,6 +67,15 @@ void FrameWriter::load_codec_options(AVDictionary **dict)
}
}

void FrameWriter::load_audio_codec_options(AVDictionary **dict)
{
for (auto& opt : params.audio_codec_options)
{
std::cout << "Setting codec option: " << opt.first << "=" << opt.second << std::endl;
av_dict_set(dict, opt.first.c_str(), opt.second.c_str(), 0);
}
}

bool is_fmt_supported(AVPixelFormat fmt, const AVPixelFormat *supported)
{
for (int i = 0; supported[i] != AV_PIX_FMT_NONE; i++)
Expand Down Expand Up @@ -316,6 +324,8 @@ void FrameWriter::init_video_stream()
videoCodecCtx->height = params.height;
videoCodecCtx->time_base = US_RATIONAL;
videoCodecCtx->color_range = AVCOL_RANGE_JPEG;
std::cout << "Framerate: " << params.framerate << std::endl;

if (params.bframes != -1)
videoCodecCtx->max_b_frames = params.bframes;

Expand Down Expand Up @@ -370,11 +380,11 @@ static uint64_t get_codec_channel_layout(const AVCodec *codec)
return codec->channel_layouts[0];
}

static enum AVSampleFormat get_codec_sample_fmt(const AVCodec *codec)
static enum AVSampleFormat get_codec_auto_sample_fmt(const AVCodec *codec)
{
int i = 0;
if (!codec->sample_fmts)
return AV_SAMPLE_FMT_S16;
return av_get_sample_fmt(FALLBACK_AUDIO_SAMPLE_FMT);
while (1) {
if (codec->sample_fmts[i] == -1)
break;
Expand All @@ -385,12 +395,44 @@ static enum AVSampleFormat get_codec_sample_fmt(const AVCodec *codec)
return codec->sample_fmts[0];
}

bool check_fmt_available(const AVCodec *codec, AVSampleFormat fmt){
for (const enum AVSampleFormat *sample_ptr = codec -> sample_fmts; *sample_ptr != -1; sample_ptr++)
{
if (*sample_ptr == fmt)
{
return true;
}
}
return false;
}

static enum AVSampleFormat convert_codec_sample_fmt(const AVCodec *codec, std::string requested_fmt)
{
static enum AVSampleFormat converted_fmt = av_get_sample_fmt(requested_fmt.c_str());
if (converted_fmt == AV_SAMPLE_FMT_NONE)
{
std::cout << "Failed to find the given sample format: " << requested_fmt << std::endl;
std::exit(-1);
} else if (!codec->sample_fmts || check_fmt_available(codec, converted_fmt))
{
std::cout << "Using sample format " << av_get_sample_fmt_name(converted_fmt) << " for audio codec " << codec->name << std::endl;
return converted_fmt;
} else
{
std::cout << "Codec " << codec->name << " does not support sample format " << av_get_sample_fmt_name(converted_fmt) << std::endl;
std::exit(-1);
}
}

void FrameWriter::init_audio_stream()
{
const AVCodec* codec = avcodec_find_encoder_by_name("aac");
AVDictionary *options = NULL;
load_codec_options(&options);

const AVCodec* codec = avcodec_find_encoder_by_name(params.audio_codec.c_str());
if (!codec)
{
std::cerr << "Failed to find the aac codec" << std::endl;
std::cerr << "Failed to find the given audio codec: " << params.audio_codec << std::endl;
std::exit(-1);
}

Expand All @@ -402,10 +444,16 @@ void FrameWriter::init_audio_stream()
}

audioCodecCtx = avcodec_alloc_context3(codec);
audioCodecCtx->bit_rate = lrintf(128000.0f);
audioCodecCtx->sample_fmt = get_codec_sample_fmt(codec);
if (params.sample_fmt.size() == 0)
{
audioCodecCtx->sample_fmt = get_codec_auto_sample_fmt(codec);
std::cout << "Choosing sample format " << av_get_sample_fmt_name(audioCodecCtx->sample_fmt) << " for audio codec " << codec->name << std::endl;
} else
{
audioCodecCtx->sample_fmt = convert_codec_sample_fmt(codec, params.sample_fmt);
}
audioCodecCtx->channel_layout = get_codec_channel_layout(codec);
audioCodecCtx->sample_rate = AUDIO_RATE;
audioCodecCtx->sample_rate = params.sample_rate;
audioCodecCtx->time_base = (AVRational) { 1, 1000 };
audioCodecCtx->channels = av_get_channel_layout_nb_channels(audioCodecCtx->channel_layout);

Expand All @@ -426,7 +474,7 @@ void FrameWriter::init_audio_stream()
std::exit(-1);
}

av_opt_set_int(swrCtx, "in_sample_rate", AUDIO_RATE, 0);
av_opt_set_int(swrCtx, "in_sample_rate", params.sample_rate, 0);
av_opt_set_int(swrCtx, "out_sample_rate", audioCodecCtx->sample_rate, 0);
av_opt_set_sample_fmt(swrCtx, "in_sample_fmt", AV_SAMPLE_FMT_FLT, 0);
av_opt_set_sample_fmt(swrCtx, "out_sample_fmt", audioCodecCtx->sample_fmt, 0);
Expand Down Expand Up @@ -623,9 +671,10 @@ bool FrameWriter::add_frame(const uint8_t* pixels, int64_t usec, bool y_invert)
#define SRC_RATE 1e6
#define DST_RATE 1e3

static int64_t conv_audio_pts(SwrContext *ctx, int64_t in)
static int64_t conv_audio_pts(SwrContext *ctx, int64_t in, int sample_rate)
{
int64_t d = (int64_t) AUDIO_RATE * AUDIO_RATE;
//int64_t d = (int64_t) AUDIO_RATE * AUDIO_RATE;
int64_t d = (int64_t) sample_rate * sample_rate;

/* Convert from audio_src_tb to 1/(src_samplerate * dst_samplerate) */
in = av_rescale_rnd(in, d, SRC_RATE, AV_ROUND_NEAR_INF);
Expand Down Expand Up @@ -655,7 +704,7 @@ size_t FrameWriter::get_audio_buffer_size()
void FrameWriter::add_audio(const void* buffer)
{
AVFrame *inputf = av_frame_alloc();
inputf->sample_rate = AUDIO_RATE;
inputf->sample_rate = params.sample_rate;
inputf->format = AV_SAMPLE_FMT_FLT;
inputf->channel_layout = AV_CH_LAYOUT_STEREO;
inputf->nb_samples = audioCodecCtx->frame_size;
Expand All @@ -670,7 +719,7 @@ void FrameWriter::add_audio(const void* buffer)
outputf->nb_samples = audioCodecCtx->frame_size;
av_frame_get_buffer(outputf, 0);

outputf->pts = conv_audio_pts(swrCtx, INT64_MIN);
outputf->pts = conv_audio_pts(swrCtx, INT64_MIN, params.sample_rate);
swr_convert_frame(swrCtx, outputf, inputf);

send_audio_pkt(outputf);
Expand Down
8 changes: 6 additions & 2 deletions src/frame-writer.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,6 @@
#include <atomic>
#include "config.h"

#define AUDIO_RATE 44100

extern "C"
{
#include <libswresample/swresample.h>
Expand Down Expand Up @@ -55,10 +53,15 @@ struct FrameWriterParams
std::string video_filter = "null"; // dummy filter

std::string codec;
std::string audio_codec;
std::string muxer;
std::string pix_fmt;
std::string sample_fmt;
std::string hw_device; // used only if codec contains vaapi
std::map<std::string, std::string> codec_options;
std::map<std::string, std::string> audio_codec_options;
int framerate;
int sample_rate;

int64_t audio_sync_offset;

Expand All @@ -76,6 +79,7 @@ class FrameWriter
{
FrameWriterParams params;
void load_codec_options(AVDictionary **dict);
void load_audio_codec_options(AVDictionary **dict);

const AVOutputFormat* outputFmt;
AVStream* videoStream;
Expand Down
Loading

0 comments on commit bf161b4

Please sign in to comment.