mirror of
https://github.com/RPCS3/rpcs3.git
synced 2024-11-22 10:42:36 +01:00
recording: initial audio recording
This commit is contained in:
parent
ebf48800e6
commit
70c35642a4
@ -33,8 +33,7 @@ void audio_resampler::put_samples(const f32* buf, u32 sample_cnt)
|
||||
|
||||
std::pair<f32* /* buffer */, u32 /* samples */> audio_resampler::get_samples(u32 sample_cnt)
|
||||
{
|
||||
f32 *const buf = resampler.bufBegin();
|
||||
return std::make_pair(buf, resampler.receiveSamples(sample_cnt));
|
||||
return std::make_pair(resampler.bufBegin(), resampler.receiveSamples(sample_cnt));
|
||||
}
|
||||
|
||||
u32 audio_resampler::samples_available() const
|
||||
|
@ -5,6 +5,7 @@
|
||||
#include "Emu/Cell/lv2/sys_process.h"
|
||||
#include "Emu/Cell/lv2/sys_event.h"
|
||||
#include "cellAudio.h"
|
||||
#include "util/video_provider.h"
|
||||
|
||||
#include <cmath>
|
||||
|
||||
@ -69,7 +70,7 @@ void cell_audio_config::reset(bool backend_changed)
|
||||
const AudioFreq freq = AudioFreq::FREQ_48K;
|
||||
const AudioSampleSize sample_size = raw.convert_to_s16 ? AudioSampleSize::S16 : AudioSampleSize::FLOAT;
|
||||
|
||||
const auto [req_ch_cnt, downmix] = AudioBackend::get_channel_count_and_downmixer(0); // CELL_AUDIO_OUT_PRIMARY
|
||||
const auto& [req_ch_cnt, downmix] = AudioBackend::get_channel_count_and_downmixer(0); // CELL_AUDIO_OUT_PRIMARY
|
||||
f64 cb_frame_len = 0.0;
|
||||
u32 ch_cnt = 2;
|
||||
|
||||
@ -276,16 +277,23 @@ void audio_ringbuffer::process_resampled_data()
|
||||
{
|
||||
if (!cfg.time_stretching_enabled) return;
|
||||
|
||||
const auto [buffer, samples] = resampler.get_samples(static_cast<u32>(cb_ringbuf.get_free_size() / (cfg.audio_sample_size * static_cast<u32>(cfg.backend_ch_cnt))));
|
||||
const auto& [buffer, samples] = resampler.get_samples(static_cast<u32>(cb_ringbuf.get_free_size() / (cfg.audio_sample_size * static_cast<u32>(cfg.backend_ch_cnt))));
|
||||
commit_data(buffer, samples);
|
||||
}
|
||||
|
||||
void audio_ringbuffer::commit_data(f32* buf, u32 sample_cnt)
|
||||
{
|
||||
sample_cnt *= cfg.audio_channels;
|
||||
const u32 sample_cnt_in = sample_cnt * cfg.audio_channels;
|
||||
const u32 sample_cnt_out = sample_cnt * static_cast<u32>(cfg.backend_ch_cnt);
|
||||
|
||||
// Dump audio if enabled
|
||||
m_dump.WriteData(buf, sample_cnt * static_cast<u32>(AudioSampleSize::FLOAT));
|
||||
m_dump.WriteData(buf, sample_cnt_in * static_cast<u32>(AudioSampleSize::FLOAT));
|
||||
|
||||
// Record audio if enabled
|
||||
if (utils::video_provider& provider = g_fxo->get<utils::video_provider>(); provider.can_consume_sample())
|
||||
{
|
||||
provider.present_samples(reinterpret_cast<u8*>(buf), sample_cnt, static_cast<u32>(cfg.audio_channels));
|
||||
}
|
||||
|
||||
if (cfg.backend_ch_cnt < AudioChannelCnt{cfg.audio_channels})
|
||||
{
|
||||
@ -293,11 +301,11 @@ void audio_ringbuffer::commit_data(f32* buf, u32 sample_cnt)
|
||||
{
|
||||
if (cfg.backend_ch_cnt == AudioChannelCnt::SURROUND_5_1)
|
||||
{
|
||||
AudioBackend::downmix<AudioChannelCnt::SURROUND_7_1, AudioChannelCnt::SURROUND_5_1>(sample_cnt, buf, buf);
|
||||
AudioBackend::downmix<AudioChannelCnt::SURROUND_7_1, AudioChannelCnt::SURROUND_5_1>(sample_cnt_in, buf, buf);
|
||||
}
|
||||
else if (cfg.backend_ch_cnt == AudioChannelCnt::STEREO)
|
||||
{
|
||||
AudioBackend::downmix<AudioChannelCnt::SURROUND_7_1, AudioChannelCnt::STEREO>(sample_cnt, buf, buf);
|
||||
AudioBackend::downmix<AudioChannelCnt::SURROUND_7_1, AudioChannelCnt::STEREO>(sample_cnt_in, buf, buf);
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -308,7 +316,7 @@ void audio_ringbuffer::commit_data(f32* buf, u32 sample_cnt)
|
||||
{
|
||||
if (cfg.backend_ch_cnt == AudioChannelCnt::STEREO)
|
||||
{
|
||||
AudioBackend::downmix<AudioChannelCnt::SURROUND_5_1, AudioChannelCnt::STEREO>(sample_cnt, buf, buf);
|
||||
AudioBackend::downmix<AudioChannelCnt::SURROUND_5_1, AudioChannelCnt::STEREO>(sample_cnt_in, buf, buf);
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -321,8 +329,6 @@ void audio_ringbuffer::commit_data(f32* buf, u32 sample_cnt)
|
||||
}
|
||||
}
|
||||
|
||||
const u32 sample_cnt_out = sample_cnt / cfg.audio_channels * static_cast<u32>(cfg.backend_ch_cnt);
|
||||
|
||||
if (cfg.backend->get_convert_to_s16())
|
||||
{
|
||||
AudioBackend::convert_to_s16(sample_cnt_out, buf, buf);
|
||||
|
@ -140,34 +140,26 @@ struct rec_param
|
||||
|
||||
constexpr u32 rec_framerate = 30; // Always 30 fps
|
||||
|
||||
class rec_image_sink : public utils::image_sink
|
||||
class rec_video_sink : public utils::video_sink
|
||||
{
|
||||
public:
|
||||
rec_image_sink() : utils::image_sink()
|
||||
rec_video_sink() : utils::video_sink()
|
||||
{
|
||||
m_framerate = rec_framerate;
|
||||
m_sample_rate = 44100; // TODO
|
||||
}
|
||||
|
||||
void stop(bool flush = true) override
|
||||
{
|
||||
cellRec.notice("Stopping image sink. flush=%d", flush);
|
||||
cellRec.notice("Stopping video sink. flush=%d", flush);
|
||||
|
||||
std::lock_guard lock(m_mtx);
|
||||
m_flush = flush;
|
||||
m_frames_to_encode.clear();
|
||||
m_samples_to_encode.clear();
|
||||
has_error = false;
|
||||
}
|
||||
|
||||
void add_frame(std::vector<u8>& frame, u32 pitch, u32 width, u32 height, s32 pixel_format, usz timestamp_ms) override
|
||||
{
|
||||
std::lock_guard lock(m_mtx);
|
||||
|
||||
if (m_flush)
|
||||
return;
|
||||
|
||||
m_frames_to_encode.emplace_back(timestamp_ms, pitch, width, height, pixel_format, std::move(frame));
|
||||
}
|
||||
|
||||
encoder_frame get_frame()
|
||||
{
|
||||
std::lock_guard lock(m_mtx);
|
||||
@ -196,7 +188,7 @@ struct rec_info
|
||||
vm::bptr<u8> video_input_buffer{}; // Used by the game to inject a frame right before it would render a frame to the screen.
|
||||
vm::bptr<u8> audio_input_buffer{}; // Used by the game to inject audio: 2-channel interleaved (left-right) * 256 samples * sizeof(f32) at 48000 kHz
|
||||
|
||||
std::vector<utils::image_sink::encoder_frame> video_ringbuffer;
|
||||
std::vector<utils::video_sink::encoder_frame> video_ringbuffer;
|
||||
std::vector<u8> audio_ringbuffer;
|
||||
usz video_ring_pos = 0;
|
||||
usz video_ring_frame_count = 0;
|
||||
@ -209,9 +201,9 @@ struct rec_info
|
||||
return pos;
|
||||
}
|
||||
|
||||
std::shared_ptr<rec_image_sink> image_sink;
|
||||
std::shared_ptr<rec_video_sink> video_sink;
|
||||
std::shared_ptr<utils::video_encoder> encoder;
|
||||
std::unique_ptr<named_thread<std::function<void()>>> image_provider_thread;
|
||||
std::unique_ptr<named_thread<std::function<void()>>> video_provider_thread;
|
||||
atomic_t<bool> paused = false;
|
||||
s64 last_pts = -1;
|
||||
|
||||
@ -240,9 +232,9 @@ struct rec_info
|
||||
void set_video_params(s32 video_format);
|
||||
void set_audio_params(s32 audio_format);
|
||||
|
||||
void start_image_provider();
|
||||
void pause_image_provider();
|
||||
void stop_image_provider(bool flush);
|
||||
void start_video_provider();
|
||||
void pause_video_provider();
|
||||
void stop_video_provider(bool flush);
|
||||
};
|
||||
|
||||
void rec_info::set_video_params(s32 video_format)
|
||||
@ -507,29 +499,29 @@ void rec_info::set_audio_params(s32 audio_format)
|
||||
cellRec.notice("set_audio_params: audio_format=0x%x, audio_codec_id=%d, sample_rate=%d, audio_bps=%d", audio_format, audio_codec_id, sample_rate, audio_bps);
|
||||
}
|
||||
|
||||
void rec_info::start_image_provider()
|
||||
void rec_info::start_video_provider()
|
||||
{
|
||||
const bool was_paused = paused.exchange(false);
|
||||
utils::video_provider& video_provider = g_fxo->get<utils::video_provider>();
|
||||
|
||||
if (image_provider_thread && was_paused)
|
||||
if (video_provider_thread && was_paused)
|
||||
{
|
||||
// Resume
|
||||
const u64 pause_time_end = get_system_time();
|
||||
ensure(pause_time_end > pause_time_start);
|
||||
pause_time_total += (pause_time_end - pause_time_start);
|
||||
video_provider.set_pause_time(pause_time_total / 1000);
|
||||
cellRec.notice("Resuming image provider.");
|
||||
cellRec.notice("Resuming video provider.");
|
||||
return;
|
||||
}
|
||||
|
||||
cellRec.notice("Starting image provider.");
|
||||
cellRec.notice("Starting video provider.");
|
||||
|
||||
recording_time_start = get_system_time();
|
||||
pause_time_total = 0;
|
||||
video_provider.set_pause_time(0);
|
||||
|
||||
image_provider_thread = std::make_unique<named_thread<std::function<void()>>>("cellRec Image Provider", [this]()
|
||||
video_provider_thread = std::make_unique<named_thread<std::function<void()>>>("cellRec video provider", [this]()
|
||||
{
|
||||
const bool use_internal_audio = param.audio_input == CELL_REC_PARAM_AUDIO_INPUT_DISABLE || param.audio_input_mix_vol < 100;
|
||||
const bool use_external_audio = param.audio_input != CELL_REC_PARAM_AUDIO_INPUT_DISABLE && param.audio_input_mix_vol > 0;
|
||||
@ -537,7 +529,7 @@ void rec_info::start_image_provider()
|
||||
const bool use_ring_buffer = param.ring_sec > 0;
|
||||
const usz frame_size = input_format.pitch * input_format.height;
|
||||
|
||||
cellRec.notice("image_provider_thread: use_ring_buffer=%d, video_ringbuffer_size=%d, audio_ringbuffer_size=%d, ring_sec=%d, frame_size=%d, use_external_video=%d, use_external_audio=%d, use_internal_audio=%d", use_ring_buffer, video_ringbuffer.size(), audio_ringbuffer.size(), param.ring_sec, frame_size, use_external_video, use_external_audio, use_internal_audio);
|
||||
cellRec.notice("video_provider_thread: use_ring_buffer=%d, video_ringbuffer_size=%d, audio_ringbuffer_size=%d, ring_sec=%d, frame_size=%d, use_external_video=%d, use_external_audio=%d, use_internal_audio=%d", use_ring_buffer, video_ringbuffer.size(), audio_ringbuffer.size(), param.ring_sec, frame_size, use_external_video, use_external_audio, use_internal_audio);
|
||||
|
||||
while (thread_ctrl::state() != thread_state::aborting && encoder)
|
||||
{
|
||||
@ -575,7 +567,7 @@ void rec_info::start_image_provider()
|
||||
{
|
||||
if (use_ring_buffer)
|
||||
{
|
||||
utils::image_sink::encoder_frame& frame_data = video_ringbuffer[next_video_ring_pos()];
|
||||
utils::video_sink::encoder_frame& frame_data = video_ringbuffer[next_video_ring_pos()];
|
||||
frame_data.pts = pts;
|
||||
frame_data.width = input_format.width;
|
||||
frame_data.height = input_format.height;
|
||||
@ -595,14 +587,14 @@ void rec_info::start_image_provider()
|
||||
last_pts = pts;
|
||||
}
|
||||
}
|
||||
else if (use_ring_buffer && image_sink)
|
||||
else if (use_ring_buffer && video_sink)
|
||||
{
|
||||
utils::image_sink::encoder_frame frame = image_sink->get_frame();
|
||||
utils::video_sink::encoder_frame frame = video_sink->get_frame();
|
||||
|
||||
if (const s64 pts = encoder->get_pts(frame.timestamp_ms); pts > last_pts && frame.data.size() > 0)
|
||||
{
|
||||
ensure(frame.data.size() == frame_size);
|
||||
utils::image_sink::encoder_frame& frame_data = video_ringbuffer[next_video_ring_pos()];
|
||||
utils::video_sink::encoder_frame& frame_data = video_ringbuffer[next_video_ring_pos()];
|
||||
frame_data = std::move(frame);
|
||||
frame_data.pts = pts;
|
||||
last_pts = pts;
|
||||
@ -635,34 +627,34 @@ void rec_info::start_image_provider()
|
||||
}
|
||||
|
||||
// Update recording time
|
||||
recording_time_total = encoder->get_timestamp_ms(encoder->last_pts());
|
||||
recording_time_total = encoder->get_timestamp_ms(encoder->last_video_pts());
|
||||
|
||||
thread_ctrl::wait_for(100);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void rec_info::pause_image_provider()
|
||||
void rec_info::pause_video_provider()
|
||||
{
|
||||
cellRec.notice("Pausing image provider.");
|
||||
|
||||
if (image_provider_thread)
|
||||
if (video_provider_thread)
|
||||
{
|
||||
paused = true;
|
||||
pause_time_start = get_system_time();
|
||||
}
|
||||
}
|
||||
|
||||
void rec_info::stop_image_provider(bool flush)
|
||||
void rec_info::stop_video_provider(bool flush)
|
||||
{
|
||||
cellRec.notice("Stopping image provider.");
|
||||
cellRec.notice("Stopping video provider.");
|
||||
|
||||
if (image_provider_thread)
|
||||
if (video_provider_thread)
|
||||
{
|
||||
auto& thread = *image_provider_thread;
|
||||
auto& thread = *video_provider_thread;
|
||||
thread = thread_state::aborting;
|
||||
thread();
|
||||
image_provider_thread.reset();
|
||||
video_provider_thread.reset();
|
||||
}
|
||||
|
||||
if (flush && param.ring_sec > 0 && !video_ringbuffer.empty())
|
||||
@ -680,7 +672,7 @@ void rec_info::stop_image_provider(bool flush)
|
||||
for (usz i = 0; i < frame_count; i++)
|
||||
{
|
||||
const usz pos = (start_offset + i) % video_ringbuffer.size();
|
||||
utils::image_sink::encoder_frame& frame_data = video_ringbuffer[pos];
|
||||
utils::video_sink::encoder_frame& frame_data = video_ringbuffer[pos];
|
||||
encoder->add_frame(frame_data.data, frame_data.pitch, frame_data.width, frame_data.height, frame_data.av_pixel_format, encoder->get_timestamp_ms(frame_data.pts - start_pts));
|
||||
|
||||
// TODO: add audio data to encoder
|
||||
@ -1073,7 +1065,7 @@ error_code cellRecOpen(vm::cptr<char> pDirName, vm::cptr<char> pFileName, vm::cp
|
||||
rec.audio_ringbuffer.resize(audio_ring_buffer_size);
|
||||
rec.audio_ring_step = audio_size_per_sample;
|
||||
rec.video_ringbuffer.resize(video_ring_buffer_size, {});
|
||||
rec.image_sink = std::make_shared<rec_image_sink>();
|
||||
rec.video_sink = std::make_shared<rec_video_sink>();
|
||||
}
|
||||
|
||||
rec.encoder = std::make_shared<utils::video_encoder>();
|
||||
@ -1082,6 +1074,7 @@ error_code cellRecOpen(vm::cptr<char> pDirName, vm::cptr<char> pFileName, vm::cp
|
||||
rec.encoder->set_video_bitrate(rec.video_bps);
|
||||
rec.encoder->set_video_codec(rec.video_codec_id);
|
||||
rec.encoder->set_sample_rate(rec.sample_rate);
|
||||
rec.encoder->set_audio_channels(rec.channels);
|
||||
rec.encoder->set_audio_bitrate(rec.audio_bps);
|
||||
rec.encoder->set_audio_codec(rec.audio_codec_id);
|
||||
rec.encoder->set_output_format(rec.output_format);
|
||||
@ -1114,12 +1107,12 @@ error_code cellRecClose(s32 isDiscard)
|
||||
if (isDiscard)
|
||||
{
|
||||
// No need to flush
|
||||
rec.stop_image_provider(false);
|
||||
rec.stop_video_provider(false);
|
||||
rec.encoder->stop(false);
|
||||
|
||||
if (rec.image_sink)
|
||||
if (rec.video_sink)
|
||||
{
|
||||
rec.image_sink->stop(false);
|
||||
rec.video_sink->stop(false);
|
||||
}
|
||||
|
||||
if (fs::is_file(rec.param.filename))
|
||||
@ -1135,18 +1128,18 @@ error_code cellRecClose(s32 isDiscard)
|
||||
else
|
||||
{
|
||||
// Flush to make sure we encode all remaining frames
|
||||
rec.stop_image_provider(true);
|
||||
rec.stop_video_provider(true);
|
||||
rec.encoder->stop(true);
|
||||
rec.recording_time_total = rec.encoder->get_timestamp_ms(rec.encoder->last_pts());
|
||||
rec.recording_time_total = rec.encoder->get_timestamp_ms(rec.encoder->last_video_pts());
|
||||
|
||||
if (rec.image_sink)
|
||||
if (rec.video_sink)
|
||||
{
|
||||
rec.image_sink->stop(true);
|
||||
rec.video_sink->stop(true);
|
||||
}
|
||||
|
||||
const s64 start_pts = rec.encoder->get_pts(rec.param.scene_metadata.start_time);
|
||||
const s64 end_pts = rec.encoder->get_pts(rec.param.scene_metadata.end_time);
|
||||
const s64 last_pts = rec.encoder->last_pts();
|
||||
const s64 last_pts = rec.encoder->last_video_pts();
|
||||
|
||||
is_valid_range = start_pts >= 0 && end_pts <= last_pts;
|
||||
}
|
||||
@ -1157,7 +1150,7 @@ error_code cellRecClose(s32 isDiscard)
|
||||
g_fxo->need<utils::video_provider>();
|
||||
utils::video_provider& video_provider = g_fxo->get<utils::video_provider>();
|
||||
|
||||
// Release the image sink if it was used
|
||||
// Release the video sink if it was used
|
||||
if (rec.param.video_input == CELL_REC_PARAM_VIDEO_INPUT_DISABLE)
|
||||
{
|
||||
const recording_mode old_mode = g_recording_mode.exchange(recording_mode::stopped);
|
||||
@ -1167,15 +1160,15 @@ error_code cellRecClose(s32 isDiscard)
|
||||
cellRec.error("cellRecClose: Unexpected recording mode %s found while stopping video capture.", old_mode);
|
||||
}
|
||||
|
||||
if (!video_provider.set_image_sink(nullptr, recording_mode::cell))
|
||||
if (!video_provider.set_video_sink(nullptr, recording_mode::cell))
|
||||
{
|
||||
cellRec.error("cellRecClose failed to release image sink");
|
||||
cellRec.error("cellRecClose failed to release video sink");
|
||||
}
|
||||
}
|
||||
|
||||
rec.param = {};
|
||||
rec.encoder.reset();
|
||||
rec.image_sink.reset();
|
||||
rec.video_sink.reset();
|
||||
rec.audio_ringbuffer.clear();
|
||||
rec.video_ringbuffer.clear();
|
||||
rec.state = rec_state::closed;
|
||||
@ -1207,7 +1200,7 @@ error_code cellRecStop()
|
||||
|
||||
sysutil_register_cb([&rec](ppu_thread& ppu) -> s32
|
||||
{
|
||||
// Disable image sink if it was used
|
||||
// Disable video sink if it was used
|
||||
if (rec.param.video_input == CELL_REC_PARAM_VIDEO_INPUT_DISABLE)
|
||||
{
|
||||
const recording_mode old_mode = g_recording_mode.exchange(recording_mode::stopped);
|
||||
@ -1219,12 +1212,12 @@ error_code cellRecStop()
|
||||
}
|
||||
|
||||
// cellRecStop actually just pauses the recording
|
||||
rec.pause_image_provider();
|
||||
rec.pause_video_provider();
|
||||
|
||||
ensure(!!rec.encoder);
|
||||
rec.encoder->pause(true);
|
||||
|
||||
rec.recording_time_total = rec.encoder->get_timestamp_ms(rec.encoder->last_pts());
|
||||
rec.recording_time_total = rec.encoder->get_timestamp_ms(rec.encoder->last_video_pts());
|
||||
rec.state = rec_state::stopped;
|
||||
|
||||
rec.cb(ppu, CELL_REC_STATUS_STOP, CELL_OK, rec.cbUserData);
|
||||
@ -1254,15 +1247,15 @@ error_code cellRecStart()
|
||||
g_fxo->need<utils::video_provider>();
|
||||
utils::video_provider& video_provider = g_fxo->get<utils::video_provider>();
|
||||
|
||||
// Setup an image sink if it is needed
|
||||
// Setup an video sink if it is needed
|
||||
if (rec.param.video_input == CELL_REC_PARAM_VIDEO_INPUT_DISABLE)
|
||||
{
|
||||
if (rec.param.ring_sec <= 0)
|
||||
{
|
||||
// Regular recording
|
||||
if (!video_provider.set_image_sink(rec.encoder, recording_mode::cell))
|
||||
if (!video_provider.set_video_sink(rec.encoder, recording_mode::cell))
|
||||
{
|
||||
cellRec.error("Failed to set image sink");
|
||||
cellRec.error("Failed to set video sink");
|
||||
rec.cb(ppu, CELL_REC_STATUS_ERR, CELL_REC_ERROR_FATAL, rec.cbUserData);
|
||||
return CELL_OK;
|
||||
}
|
||||
@ -1270,9 +1263,9 @@ error_code cellRecStart()
|
||||
else
|
||||
{
|
||||
// Ringbuffer recording
|
||||
if (!video_provider.set_image_sink(rec.image_sink, recording_mode::cell))
|
||||
if (!video_provider.set_video_sink(rec.video_sink, recording_mode::cell))
|
||||
{
|
||||
cellRec.error("Failed to set image sink");
|
||||
cellRec.error("Failed to set video sink");
|
||||
rec.cb(ppu, CELL_REC_STATUS_ERR, CELL_REC_ERROR_FATAL, rec.cbUserData);
|
||||
return CELL_OK;
|
||||
}
|
||||
@ -1287,7 +1280,7 @@ error_code cellRecStart()
|
||||
g_recording_mode = recording_mode::stopped;
|
||||
}
|
||||
|
||||
rec.start_image_provider();
|
||||
rec.start_video_provider();
|
||||
|
||||
if (rec.encoder->has_error)
|
||||
{
|
||||
|
@ -8,14 +8,31 @@ struct cfg_recording final : cfg::node
|
||||
bool load();
|
||||
void save() const;
|
||||
|
||||
cfg::uint<0, 60> framerate{this, "Framerate", 30};
|
||||
cfg::uint<0, 7680> width{this, "Width", 1280};
|
||||
cfg::uint<0, 4320> height{this, "Height", 720};
|
||||
cfg::uint<0, 192> pixel_format{this, "AVPixelFormat", 0}; // AVPixelFormat::AV_PIX_FMT_YUV420P
|
||||
cfg::uint<0, 32813> video_codec{this, "AVCodecID", 12}; // AVCodecID::AV_CODEC_ID_MPEG4
|
||||
cfg::uint<0, 25000000> video_bps{this, "Video Bitrate", 4000000};
|
||||
cfg::uint<0, 5> max_b_frames{this, "Max B-Frames", 2};
|
||||
cfg::uint<0, 20> gop_size{this, "Group of Pictures Size", 12};
|
||||
struct node_video : cfg::node
|
||||
{
|
||||
node_video(cfg::node* _this) : cfg::node(_this, "Video") {}
|
||||
|
||||
cfg::uint<0, 60> framerate{this, "Framerate", 30};
|
||||
cfg::uint<0, 7680> width{this, "Width", 1280};
|
||||
cfg::uint<0, 4320> height{this, "Height", 720};
|
||||
cfg::uint<0, 192> pixel_format{this, "AVPixelFormat", 0}; // AVPixelFormat::AV_PIX_FMT_YUV420P
|
||||
cfg::uint<0, 0xFFFF> video_codec{this, "AVCodecID", 12}; // AVCodecID::AV_CODEC_ID_MPEG4
|
||||
cfg::uint<0, 25000000> video_bps{this, "Video Bitrate", 4000000};
|
||||
cfg::uint<0, 5> max_b_frames{this, "Max B-Frames", 2};
|
||||
cfg::uint<0, 20> gop_size{this, "Group of Pictures Size", 12};
|
||||
|
||||
} video{ this };
|
||||
|
||||
struct node_audio : cfg::node
|
||||
{
|
||||
node_audio(cfg::node* _this) : cfg::node(_this, "Audio") {}
|
||||
|
||||
cfg::uint<0x10000, 0x17000> audio_codec{this, "AVCodecID", 86019}; // AVCodecID::AV_CODEC_ID_AC3
|
||||
cfg::uint<0, 8> channels{this, "Channels", 2};
|
||||
cfg::uint<0, 25000000> audio_bps{this, "Audio Bitrate", 320000};
|
||||
cfg::uint<0, 25000000> sample_rate{this, "Sample Rate", 48000};
|
||||
|
||||
} audio{ this };
|
||||
|
||||
const std::string path;
|
||||
};
|
||||
|
@ -618,7 +618,7 @@
|
||||
<ClInclude Include="Loader\mself.hpp" />
|
||||
<ClInclude Include="util\atomic.hpp" />
|
||||
<ClInclude Include="util\bless.hpp" />
|
||||
<ClInclude Include="util\image_sink.h" />
|
||||
<ClInclude Include="util\video_sink.h" />
|
||||
<ClInclude Include="util\video_provider.h" />
|
||||
<ClInclude Include="util\media_utils.h" />
|
||||
<ClInclude Include="util\serialization.hpp" />
|
||||
|
@ -2275,7 +2275,7 @@
|
||||
<ClInclude Include="util\video_provider.h">
|
||||
<Filter>Utilities</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="util\image_sink.h">
|
||||
<ClInclude Include="util\video_sink.h">
|
||||
<Filter>Utilities</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="Emu\Io\recording_config.h">
|
||||
|
@ -12,6 +12,7 @@
|
||||
#include "Emu/IdManager.h"
|
||||
#include "Emu/Cell/Modules/cellScreenshot.h"
|
||||
#include "Emu/Cell/Modules/cellVideoOut.h"
|
||||
#include "Emu/Cell/Modules/cellAudio.h"
|
||||
#include "Emu/RSX/rsx_utils.h"
|
||||
#include "Emu/RSX/Overlays/overlay_message.h"
|
||||
#include "Emu/Io/recording_config.h"
|
||||
@ -445,9 +446,9 @@ void gs_frame::toggle_recording()
|
||||
{
|
||||
m_video_encoder->stop();
|
||||
|
||||
if (!video_provider.set_image_sink(nullptr, recording_mode::rpcs3))
|
||||
if (!video_provider.set_video_sink(nullptr, recording_mode::rpcs3))
|
||||
{
|
||||
gui_log.warning("The video provider could not release the image sink. A sink with higher priority must have been set.");
|
||||
gui_log.warning("The video provider could not release the video sink. A sink with higher priority must have been set.");
|
||||
}
|
||||
|
||||
// Play a sound
|
||||
@ -489,21 +490,23 @@ void gs_frame::toggle_recording()
|
||||
video_path += "recording_" + date_time::current_time_narrow<'_'>() + ".mp4";
|
||||
|
||||
utils::video_encoder::frame_format output_format{};
|
||||
output_format.av_pixel_format = static_cast<AVPixelFormat>(g_cfg_recording.pixel_format.get());
|
||||
output_format.width = g_cfg_recording.width;
|
||||
output_format.height = g_cfg_recording.height;
|
||||
output_format.pitch = g_cfg_recording.width * 4;
|
||||
output_format.av_pixel_format = static_cast<AVPixelFormat>(g_cfg_recording.video.pixel_format.get());
|
||||
output_format.width = g_cfg_recording.video.width;
|
||||
output_format.height = g_cfg_recording.video.height;
|
||||
output_format.pitch = g_cfg_recording.video.width * 4;
|
||||
|
||||
m_video_encoder->set_path(video_path);
|
||||
m_video_encoder->set_framerate(g_cfg_recording.framerate);
|
||||
m_video_encoder->set_video_bitrate(g_cfg_recording.video_bps);
|
||||
m_video_encoder->set_video_codec(g_cfg_recording.video_codec);
|
||||
m_video_encoder->set_max_b_frames(g_cfg_recording.max_b_frames);
|
||||
m_video_encoder->set_gop_size(g_cfg_recording.gop_size);
|
||||
m_video_encoder->set_framerate(g_cfg_recording.video.framerate);
|
||||
m_video_encoder->set_video_bitrate(g_cfg_recording.video.video_bps);
|
||||
m_video_encoder->set_video_codec(g_cfg_recording.video.video_codec);
|
||||
m_video_encoder->set_max_b_frames(g_cfg_recording.video.max_b_frames);
|
||||
m_video_encoder->set_gop_size(g_cfg_recording.video.gop_size);
|
||||
m_video_encoder->set_output_format(output_format);
|
||||
m_video_encoder->set_sample_rate(0); // TODO
|
||||
m_video_encoder->set_audio_bitrate(0); // TODO
|
||||
m_video_encoder->set_audio_codec(0); // TODO
|
||||
m_video_encoder->set_sample_rate(g_cfg_recording.audio.sample_rate);
|
||||
//m_video_encoder->set_audio_channels(static_cast<u32>(g_fxo->get<cell_audio>().cfg.backend_ch_cnt));
|
||||
m_video_encoder->set_audio_channels(static_cast<u32>(g_fxo->get<cell_audio>().cfg.audio_channels));
|
||||
m_video_encoder->set_audio_bitrate(g_cfg_recording.audio.audio_bps);
|
||||
m_video_encoder->set_audio_codec(g_cfg_recording.audio.audio_codec);
|
||||
m_video_encoder->encode();
|
||||
|
||||
if (m_video_encoder->has_error)
|
||||
@ -513,9 +516,9 @@ void gs_frame::toggle_recording()
|
||||
return;
|
||||
}
|
||||
|
||||
if (!video_provider.set_image_sink(m_video_encoder, recording_mode::rpcs3))
|
||||
if (!video_provider.set_video_sink(m_video_encoder, recording_mode::rpcs3))
|
||||
{
|
||||
gui_log.warning("The video provider could not set the image sink. A sink with higher priority must have been set.");
|
||||
gui_log.warning("The video provider could not set the video sink. A sink with higher priority must have been set.");
|
||||
rsx::overlays::queue_message(tr("Recording not possible").toStdString());
|
||||
m_video_encoder->stop();
|
||||
return;
|
||||
|
@ -1,54 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include "util/types.hpp"
|
||||
#include "util/atomic.hpp"
|
||||
#include "Utilities/mutex.h"
|
||||
|
||||
#include <deque>
|
||||
#include <cmath>
|
||||
|
||||
namespace utils
|
||||
{
|
||||
class image_sink
|
||||
{
|
||||
public:
|
||||
image_sink() = default;
|
||||
|
||||
virtual void stop(bool flush = true) = 0;
|
||||
virtual void add_frame(std::vector<u8>& frame, u32 pitch, u32 width, u32 height, s32 pixel_format, usz timestamp_ms) = 0;
|
||||
|
||||
s64 get_pts(usz timestamp_ms) const
|
||||
{
|
||||
return static_cast<s64>(std::round((timestamp_ms * m_framerate) / 1000.f));
|
||||
}
|
||||
|
||||
usz get_timestamp_ms(s64 pts) const
|
||||
{
|
||||
return static_cast<usz>(std::round((pts * 1000) / static_cast<float>(m_framerate)));
|
||||
}
|
||||
|
||||
atomic_t<bool> has_error{false};
|
||||
|
||||
struct encoder_frame
|
||||
{
|
||||
encoder_frame() = default;
|
||||
encoder_frame(usz timestamp_ms, u32 pitch, u32 width, u32 height, s32 av_pixel_format, std::vector<u8>&& data)
|
||||
: timestamp_ms(timestamp_ms), pitch(pitch), width(width), height(height), av_pixel_format(av_pixel_format), data(std::move(data))
|
||||
{}
|
||||
|
||||
s64 pts = -1; // Optional
|
||||
usz timestamp_ms = 0;
|
||||
u32 pitch = 0;
|
||||
u32 width = 0;
|
||||
u32 height = 0;
|
||||
s32 av_pixel_format = 0; // NOTE: Make sure this is a valid AVPixelFormat
|
||||
std::vector<u8> data;
|
||||
};
|
||||
|
||||
protected:
|
||||
shared_mutex m_mtx;
|
||||
std::deque<encoder_frame> m_frames_to_encode;
|
||||
atomic_t<bool> m_flush = false;
|
||||
u32 m_framerate = 0;
|
||||
};
|
||||
}
|
File diff suppressed because it is too large
Load Diff
@ -88,7 +88,7 @@ namespace utils
|
||||
std::unique_ptr<named_thread<std::function<void()>>> m_thread;
|
||||
};
|
||||
|
||||
class video_encoder : public utils::image_sink
|
||||
class video_encoder : public utils::video_sink
|
||||
{
|
||||
public:
|
||||
video_encoder();
|
||||
@ -108,7 +108,7 @@ namespace utils
|
||||
};
|
||||
|
||||
std::string path() const;
|
||||
s64 last_pts() const;
|
||||
s64 last_video_pts() const;
|
||||
|
||||
void set_path(const std::string& path);
|
||||
void set_framerate(u32 framerate);
|
||||
@ -118,16 +118,17 @@ namespace utils
|
||||
void set_max_b_frames(s32 max_b_frames);
|
||||
void set_gop_size(s32 gop_size);
|
||||
void set_sample_rate(u32 sample_rate);
|
||||
void set_audio_channels(u32 channels);
|
||||
void set_audio_bitrate(u32 bitrate);
|
||||
void set_audio_codec(s32 codec_id);
|
||||
void add_frame(std::vector<u8>& frame, u32 pitch, u32 width, u32 height, s32 pixel_format, usz timestamp_ms) override;
|
||||
void pause(bool flush = true);
|
||||
void stop(bool flush = true) override;
|
||||
void encode();
|
||||
|
||||
private:
|
||||
std::string m_path;
|
||||
s64 m_last_pts = 0;
|
||||
s64 m_last_audio_pts = 0;
|
||||
s64 m_last_video_pts = 0;
|
||||
|
||||
// Thread control
|
||||
std::unique_ptr<named_thread<std::function<void()>>> m_thread;
|
||||
@ -136,14 +137,14 @@ namespace utils
|
||||
|
||||
// Video parameters
|
||||
u32 m_video_bitrate_bps = 0;
|
||||
s32 m_video_codec_id = 12; // AV_CODEC_ID_MPEG4;
|
||||
s32 m_video_codec_id = 12; // AV_CODEC_ID_MPEG4
|
||||
s32 m_max_b_frames = 2;
|
||||
s32 m_gop_size = 12;
|
||||
frame_format m_out_format{};
|
||||
|
||||
// Audio parameters
|
||||
u32 m_sample_rate = 48000;
|
||||
u32 m_audio_bitrate_bps = 96000;
|
||||
s32 m_audio_codec_id = 86018; // AV_CODEC_ID_AAC
|
||||
u32 m_channels = 2;
|
||||
u32 m_audio_bitrate_bps = 320000;
|
||||
s32 m_audio_codec_id = 86019; // AV_CODEC_ID_AC3
|
||||
};
|
||||
}
|
||||
|
@ -34,37 +34,37 @@ namespace utils
|
||||
g_recording_mode = recording_mode::stopped;
|
||||
}
|
||||
|
||||
bool video_provider::set_image_sink(std::shared_ptr<image_sink> sink, recording_mode type)
|
||||
bool video_provider::set_video_sink(std::shared_ptr<video_sink> sink, recording_mode type)
|
||||
{
|
||||
media_log.notice("video_provider: setting new image sink. sink=%d, type=%s", !!sink, type);
|
||||
media_log.notice("video_provider: setting new video sink. sink=%d, type=%s", !!sink, type);
|
||||
|
||||
if (type == recording_mode::stopped)
|
||||
{
|
||||
// Prevent misuse. type is supposed to be a valid state.
|
||||
media_log.error("video_provider: cannot set image sink with type %s", type);
|
||||
media_log.error("video_provider: cannot set video sink with type %s", type);
|
||||
return false;
|
||||
}
|
||||
|
||||
std::lock_guard lock(m_mutex);
|
||||
|
||||
if (m_image_sink)
|
||||
if (m_video_sink)
|
||||
{
|
||||
// cell has preference
|
||||
if (m_type == recording_mode::cell && m_type != type)
|
||||
{
|
||||
media_log.warning("video_provider: cannot set image sink with type %s if type %s is active", type, m_type);
|
||||
media_log.warning("video_provider: cannot set video sink with type %s if type %s is active", type, m_type);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (m_type != type || m_image_sink != sink)
|
||||
if (m_type != type || m_video_sink != sink)
|
||||
{
|
||||
media_log.warning("video_provider: stopping current image sink of type %s", m_type);
|
||||
m_image_sink->stop();
|
||||
media_log.warning("video_provider: stopping current video sink of type %s", m_type);
|
||||
m_video_sink->stop();
|
||||
}
|
||||
}
|
||||
|
||||
m_type = sink ? type : recording_mode::stopped;
|
||||
m_image_sink = sink;
|
||||
m_video_sink = sink;
|
||||
|
||||
if (m_type == recording_mode::stopped)
|
||||
{
|
||||
@ -84,19 +84,17 @@ namespace utils
|
||||
{
|
||||
std::lock_guard lock(m_mutex);
|
||||
|
||||
if (!m_image_sink)
|
||||
if (!m_video_sink)
|
||||
return false;
|
||||
|
||||
const usz timestamp_ms = std::chrono::duration_cast<std::chrono::milliseconds>(steady_clock::now() - m_encoder_start).count() - m_pause_time_ms;
|
||||
const s64 pts = m_image_sink->get_pts(timestamp_ms);
|
||||
return pts > m_last_pts_incoming;
|
||||
const s64 pts = m_video_sink->get_pts(timestamp_ms);
|
||||
return pts > m_last_video_pts_incoming;
|
||||
}
|
||||
|
||||
void video_provider::present_frame(std::vector<u8>& data, u32 pitch, u32 width, u32 height, bool is_bgra)
|
||||
recording_mode video_provider::check_state()
|
||||
{
|
||||
std::lock_guard lock(m_mutex);
|
||||
|
||||
if (!m_image_sink || m_image_sink->has_error)
|
||||
if (!m_video_sink || m_video_sink->has_error)
|
||||
{
|
||||
g_recording_mode = recording_mode::stopped;
|
||||
rsx::overlays::queue_message(localized_string_id::RECORDING_ABORTED);
|
||||
@ -105,33 +103,86 @@ namespace utils
|
||||
if (g_recording_mode == recording_mode::stopped)
|
||||
{
|
||||
m_active = false;
|
||||
return;
|
||||
return g_recording_mode;
|
||||
}
|
||||
|
||||
if (!m_active.exchange(true))
|
||||
{
|
||||
m_current_encoder_frame = 0;
|
||||
m_last_pts_incoming = -1;
|
||||
m_last_video_pts_incoming = -1;
|
||||
m_last_audio_pts_incoming = -1;
|
||||
}
|
||||
|
||||
if (m_current_encoder_frame == 0)
|
||||
if (m_current_encoder_frame == 0 && m_current_encoder_sample == 0)
|
||||
{
|
||||
m_encoder_start = steady_clock::now();
|
||||
}
|
||||
|
||||
// Calculate presentation timestamp.
|
||||
const usz timestamp_ms = std::chrono::duration_cast<std::chrono::milliseconds>(steady_clock::now() - m_encoder_start).count() - m_pause_time_ms;
|
||||
const s64 pts = m_image_sink->get_pts(timestamp_ms);
|
||||
return g_recording_mode;
|
||||
}
|
||||
|
||||
// We can just skip this frame if it has the same timestamp.
|
||||
if (pts <= m_last_pts_incoming)
|
||||
void video_provider::present_frame(std::vector<u8>& data, u32 pitch, u32 width, u32 height, bool is_bgra)
|
||||
{
|
||||
std::lock_guard lock(m_mutex);
|
||||
|
||||
if (check_state() == recording_mode::stopped)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
m_last_pts_incoming = pts;
|
||||
// Calculate presentation timestamp.
|
||||
const usz timestamp_ms = std::chrono::duration_cast<std::chrono::milliseconds>(steady_clock::now() - m_encoder_start).count() - m_pause_time_ms;
|
||||
const s64 pts = m_video_sink->get_pts(timestamp_ms);
|
||||
|
||||
// We can just skip this frame if it has the same timestamp.
|
||||
if (pts <= m_last_video_pts_incoming)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
m_last_video_pts_incoming = pts;
|
||||
m_current_encoder_frame++;
|
||||
m_image_sink->add_frame(data, pitch, width, height, is_bgra ? AVPixelFormat::AV_PIX_FMT_BGRA : AVPixelFormat::AV_PIX_FMT_RGBA, timestamp_ms);
|
||||
m_video_sink->add_frame(data, pitch, width, height, is_bgra ? AVPixelFormat::AV_PIX_FMT_BGRA : AVPixelFormat::AV_PIX_FMT_RGBA, timestamp_ms);
|
||||
}
|
||||
|
||||
bool video_provider::can_consume_sample()
|
||||
{
|
||||
std::lock_guard lock(m_mutex);
|
||||
|
||||
if (!m_video_sink)
|
||||
return false;
|
||||
|
||||
const usz timestamp_us = std::chrono::duration_cast<std::chrono::microseconds>(steady_clock::now() - m_encoder_start).count() - (m_pause_time_ms * 1000ull);
|
||||
const s64 pts = m_video_sink->get_audio_pts(timestamp_us);
|
||||
return pts > m_last_audio_pts_incoming;
|
||||
}
|
||||
|
||||
void video_provider::present_samples(u8* buf, u32 sample_count, u16 channels)
|
||||
{
|
||||
if (!buf || !sample_count || !channels)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
std::lock_guard lock(m_mutex);
|
||||
|
||||
if (check_state() == recording_mode::stopped)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Calculate presentation timestamp.
|
||||
const usz timestamp_us = std::chrono::duration_cast<std::chrono::microseconds>(steady_clock::now() - m_encoder_start).count() - (m_pause_time_ms * 1000ull);
|
||||
const s64 pts = m_video_sink->get_audio_pts(timestamp_us);
|
||||
|
||||
// We can just skip this sample if it has the same timestamp.
|
||||
if (pts <= m_last_audio_pts_incoming)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
m_last_audio_pts_incoming = pts;
|
||||
m_current_encoder_sample += sample_count;
|
||||
m_video_sink->add_audio_samples(buf, sample_count, channels, timestamp_us);
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
#pragma once
|
||||
|
||||
#include "image_sink.h"
|
||||
#include "video_sink.h"
|
||||
|
||||
enum class recording_mode
|
||||
{
|
||||
@ -17,19 +17,27 @@ namespace utils
|
||||
video_provider() = default;
|
||||
~video_provider();
|
||||
|
||||
bool set_image_sink(std::shared_ptr<image_sink> sink, recording_mode type);
|
||||
bool set_video_sink(std::shared_ptr<video_sink> sink, recording_mode type);
|
||||
void set_pause_time(usz pause_time_ms);
|
||||
|
||||
bool can_consume_frame();
|
||||
void present_frame(std::vector<u8>& data, u32 pitch, u32 width, u32 height, bool is_bgra);
|
||||
|
||||
bool can_consume_sample();
|
||||
void present_samples(u8* buf, u32 sample_count, u16 channels);
|
||||
|
||||
private:
|
||||
recording_mode check_state();
|
||||
|
||||
recording_mode m_type = recording_mode::stopped;
|
||||
std::shared_ptr<image_sink> m_image_sink;
|
||||
std::shared_ptr<video_sink> m_video_sink;
|
||||
shared_mutex m_mutex{};
|
||||
atomic_t<bool> m_active{false};
|
||||
atomic_t<usz> m_current_encoder_frame{0};
|
||||
atomic_t<usz> m_current_encoder_sample{0};
|
||||
steady_clock::time_point m_encoder_start{};
|
||||
s64 m_last_pts_incoming = -1;
|
||||
s64 m_last_video_pts_incoming = -1;
|
||||
s64 m_last_audio_pts_incoming = -1;
|
||||
usz m_pause_time_ms = 0;
|
||||
};
|
||||
|
||||
|
104
rpcs3/util/video_sink.h
Normal file
104
rpcs3/util/video_sink.h
Normal file
@ -0,0 +1,104 @@
|
||||
#pragma once
|
||||
|
||||
#include "util/types.hpp"
|
||||
#include "util/atomic.hpp"
|
||||
#include "Utilities/mutex.h"
|
||||
|
||||
#include <deque>
|
||||
#include <cmath>
|
||||
|
||||
namespace utils
|
||||
{
|
||||
class video_sink
|
||||
{
|
||||
public:
|
||||
video_sink() = default;
|
||||
|
||||
virtual void stop(bool flush = true) = 0;
|
||||
|
||||
void add_frame(std::vector<u8>& frame, u32 pitch, u32 width, u32 height, s32 pixel_format, usz timestamp_ms)
|
||||
{
|
||||
// Do not allow new frames while flushing
|
||||
if (m_flush)
|
||||
return;
|
||||
|
||||
std::lock_guard lock(m_mtx);
|
||||
m_frames_to_encode.emplace_back(timestamp_ms, pitch, width, height, pixel_format, std::move(frame));
|
||||
}
|
||||
|
||||
void add_audio_samples(u8* buf, u32 sample_count, u16 channels, usz timestamp_us)
|
||||
{
|
||||
// Do not allow new samples while flushing
|
||||
if (m_flush || !buf || !sample_count || !channels)
|
||||
return;
|
||||
|
||||
std::vector<u8> sample(buf, buf + sample_count * channels * sizeof(f32));
|
||||
std::lock_guard lock(m_audio_mtx);
|
||||
m_samples_to_encode.emplace_back(timestamp_us, sample_count, channels, std::move(sample));
|
||||
}
|
||||
|
||||
s64 get_pts(usz timestamp_ms) const
|
||||
{
|
||||
return static_cast<s64>(std::round((timestamp_ms * m_framerate) / 1000.f));
|
||||
}
|
||||
|
||||
s64 get_audio_pts(usz timestamp_us) const
|
||||
{
|
||||
static constexpr f32 us_per_sec = 1000000.0f;
|
||||
const f32 us_per_block = us_per_sec / (m_sample_rate / static_cast<f32>(m_samples_per_block));
|
||||
return static_cast<s64>(std::ceil(timestamp_us / us_per_block));
|
||||
}
|
||||
|
||||
usz get_timestamp_ms(s64 pts) const
|
||||
{
|
||||
return static_cast<usz>(std::round((pts * 1000) / static_cast<float>(m_framerate)));
|
||||
}
|
||||
|
||||
usz get_audio_timestamp_us(s64 pts) const
|
||||
{
|
||||
return static_cast<usz>(std::round((pts * 1000) / static_cast<float>(m_sample_rate)));
|
||||
}
|
||||
|
||||
atomic_t<bool> has_error{false};
|
||||
|
||||
struct encoder_frame
|
||||
{
|
||||
encoder_frame() = default;
|
||||
encoder_frame(usz timestamp_ms, u32 pitch, u32 width, u32 height, s32 av_pixel_format, std::vector<u8>&& data)
|
||||
: timestamp_ms(timestamp_ms), pitch(pitch), width(width), height(height), av_pixel_format(av_pixel_format), data(std::move(data))
|
||||
{}
|
||||
|
||||
s64 pts = -1; // Optional
|
||||
usz timestamp_ms = 0;
|
||||
u32 pitch = 0;
|
||||
u32 width = 0;
|
||||
u32 height = 0;
|
||||
s32 av_pixel_format = 0; // NOTE: Make sure this is a valid AVPixelFormat
|
||||
std::vector<u8> data;
|
||||
};
|
||||
|
||||
struct encoder_sample
|
||||
{
|
||||
encoder_sample() = default;
|
||||
encoder_sample(usz timestamp_us, u32 sample_count, u16 channels, std::vector<u8>&& data)
|
||||
: timestamp_us(timestamp_us), sample_count(sample_count), channels(channels), data(std::move(data))
|
||||
{
|
||||
}
|
||||
|
||||
usz timestamp_us = 0;
|
||||
u32 sample_count = 0;
|
||||
u16 channels = 0;
|
||||
std::vector<u8> data;
|
||||
};
|
||||
|
||||
protected:
|
||||
shared_mutex m_mtx;
|
||||
std::deque<encoder_frame> m_frames_to_encode;
|
||||
shared_mutex m_audio_mtx;
|
||||
std::deque<encoder_sample> m_samples_to_encode;
|
||||
atomic_t<bool> m_flush = false;
|
||||
u32 m_framerate = 30;
|
||||
u32 m_sample_rate = 48000;
|
||||
static constexpr u32 m_samples_per_block = 256;
|
||||
};
|
||||
}
|
Loading…
Reference in New Issue
Block a user