diff --git a/src/util/media_capture.cpp b/src/util/media_capture.cpp index 77f7ef635..1cd3eb501 100644 --- a/src/util/media_capture.cpp +++ b/src/util/media_capture.cpp @@ -634,7 +634,7 @@ class MediaCaptureMF final : public MediaCaptureBase template using ComPtr = Microsoft::WRL::ComPtr; - static constexpr u32 TEN_NANOSECONDS = 10 * 1000 * 1000; + static constexpr u32 FRAME_RATE_NUMERATOR = 10 * 1000 * 1000; static constexpr DWORD INVALID_STREAM_INDEX = std::numeric_limits::max(); static constexpr u32 AUDIO_BITS_PER_SAMPLE = sizeof(s16) * 8; @@ -665,10 +665,26 @@ protected: bool InternalEndCapture(std::unique_lock& lock, Error* error) override; private: - ComPtr CreateVideoYUVTransform(ComPtr* output_type, Error* error); - ComPtr CreateVideoEncodeTransform(std::string_view codec, u32 bitrate, IMFMediaType* input_type, - ComPtr* output_type, bool* use_async_transform, - Error* error); + // Media foundation works in units of 100 nanoseconds. + static constexpr double ConvertFrequencyToMFDurationUnits(double frequency) { return ((1e+9 / frequency) / 100.0); } + static constexpr LONGLONG ConvertPTSToTimestamp(s64 pts, double duration) + { + // Both of these use truncation, not rounding, so that the next sample lines up. + return static_cast(static_cast(pts) * duration); + } + static constexpr LONGLONG ConvertFramesToDuration(u32 frames, double duration) + { + return static_cast(static_cast(frames) * duration); + } + static constexpr time_t ConvertPTSToSeconds(s64 pts, double duration) + { + return static_cast((static_cast(pts) * duration) / 1e+7); + } + + ComPtr CreateVideoYUVTransform(ComPtr* output_type, float fps, Error* error); + ComPtr CreateVideoEncodeTransform(std::string_view codec, float fps, u32 bitrate, + IMFMediaType* input_type, ComPtr* output_type, + bool* use_async_transform, Error* error); bool GetAudioTypes(std::string_view codec, ComPtr* input_type, ComPtr* output_type, u32 sample_rate, u32 bitrate, Error* error); void ConvertVideoFrame(u8* dst, size_t dst_stride, const u8* src, size_t src_stride, u32 width, u32 height) const; @@ -681,10 +697,8 @@ private: DWORD m_video_stream_index = INVALID_STREAM_INDEX; DWORD m_audio_stream_index = INVALID_STREAM_INDEX; - LONGLONG m_video_sample_duration = 0; - LONGLONG m_audio_sample_duration = 0; - - u32 m_frame_rate_numerator = 0; + double m_video_sample_duration = 0; + double m_audio_sample_duration = 0; ComPtr m_video_yuv_transform; ComPtr m_video_yuv_sample; @@ -854,9 +868,9 @@ bool MediaCaptureMF::IsCapturingAudio() const time_t MediaCaptureMF::GetElapsedTime() const { if (IsCapturingVideo()) - return static_cast(static_cast(m_next_video_pts * m_video_sample_duration) / TEN_NANOSECONDS); + return ConvertPTSToSeconds(m_next_video_pts, m_video_sample_duration); else - return static_cast(static_cast(m_next_audio_pts * m_audio_sample_duration) / TEN_NANOSECONDS); + return ConvertPTSToSeconds(m_next_audio_pts, m_audio_sample_duration); } bool MediaCaptureMF::InternalBeginCapture(float fps, float aspect, u32 sample_rate, bool capture_video, @@ -872,12 +886,11 @@ bool MediaCaptureMF::InternalBeginCapture(float fps, float aspect, u32 sample_ra if (capture_video) { - m_frame_rate_numerator = static_cast(fps * TEN_NANOSECONDS); - m_video_sample_duration = static_cast(static_cast(TEN_NANOSECONDS) / static_cast(fps)); + m_video_sample_duration = ConvertFrequencyToMFDurationUnits(fps); ComPtr yuv_media_type; - if (!(m_video_yuv_transform = CreateVideoYUVTransform(&yuv_media_type, error)) || - !(m_video_encode_transform = CreateVideoEncodeTransform(video_codec, video_bitrate, yuv_media_type.Get(), + if (!(m_video_yuv_transform = CreateVideoYUVTransform(&yuv_media_type, fps, error)) || + !(m_video_encode_transform = CreateVideoEncodeTransform(video_codec, fps, video_bitrate, yuv_media_type.Get(), &video_media_type, &use_async_video_transform, error))) { return false; @@ -892,9 +905,7 @@ bool MediaCaptureMF::InternalBeginCapture(float fps, float aspect, u32 sample_ra // only used when not capturing video m_audio_frame_size = static_cast(static_cast(sample_rate) / fps); - - m_audio_sample_duration = - static_cast(static_cast(TEN_NANOSECONDS) / static_cast(sample_rate)); + m_audio_sample_duration = ConvertFrequencyToMFDurationUnits(sample_rate); } if (FAILED(hr = wrap_MFCreateSinkWriterFromURL(StringUtil::UTF8StringToWideString(m_path).c_str(), nullptr, nullptr, @@ -987,7 +998,7 @@ bool MediaCaptureMF::InternalEndCapture(std::unique_lock& lock, Erro } MediaCaptureMF::ComPtr MediaCaptureMF::CreateVideoYUVTransform(ComPtr* output_type, - Error* error) + float fps, Error* error) { const MFT_REGISTER_TYPE_INFO input_type_info = {.guidMajorType = MFMediaType_Video, .guidSubtype = VIDEO_RGB_MEDIA_FORMAT}; @@ -1035,8 +1046,10 @@ MediaCaptureMF::ComPtr MediaCaptureMF::CreateVideoYUVTransform(Com FAILED(hr = (*output_type)->SetGUID(MF_MT_SUBTYPE, VIDEO_YUV_MEDIA_FORMAT)) || FAILED(hr = (*output_type)->SetUINT32(MF_MT_INTERLACE_MODE, MFVideoInterlace_Progressive)) || FAILED(hr = MFSetAttributeSize(output_type->Get(), MF_MT_FRAME_SIZE, m_video_width, m_video_height)) || - FAILED(hr = MFSetAttributeRatio(output_type->Get(), MF_MT_FRAME_RATE, m_frame_rate_numerator, TEN_NANOSECONDS))) - [[unlikely]] + FAILED(hr = MFSetAttributeRatio( + output_type->Get(), MF_MT_FRAME_RATE, + static_cast(static_cast(fps) * static_cast(FRAME_RATE_NUMERATOR)), + FRAME_RATE_NUMERATOR))) [[unlikely]] { Error::SetHResult(error, "YUV setting attributes failed: ", hr); return nullptr; @@ -1057,8 +1070,8 @@ MediaCaptureMF::ComPtr MediaCaptureMF::CreateVideoYUVTransform(Com return transform; } -MediaCaptureMF::ComPtr MediaCaptureMF::CreateVideoEncodeTransform(std::string_view codec, u32 bitrate, - IMFMediaType* input_type, +MediaCaptureMF::ComPtr MediaCaptureMF::CreateVideoEncodeTransform(std::string_view codec, float fps, + u32 bitrate, IMFMediaType* input_type, ComPtr* output_type, bool* use_async_transform, Error* error) { @@ -1152,7 +1165,10 @@ MediaCaptureMF::ComPtr MediaCaptureMF::CreateVideoEncodeTransform( FAILED(hr = (*output_type)->SetUINT32(MF_MT_INTERLACE_MODE, MFVideoInterlace_Progressive)) || FAILED(hr = (*output_type)->SetUINT32(MF_MT_MPEG2_PROFILE, profile)) || FAILED(hr = MFSetAttributeSize(output_type->Get(), MF_MT_FRAME_SIZE, m_video_width, m_video_height)) || - FAILED(hr = MFSetAttributeRatio(output_type->Get(), MF_MT_FRAME_RATE, m_frame_rate_numerator, TEN_NANOSECONDS)) || + FAILED(hr = MFSetAttributeRatio( + output_type->Get(), MF_MT_FRAME_RATE, + static_cast(static_cast(fps) * static_cast(FRAME_RATE_NUMERATOR)), + FRAME_RATE_NUMERATOR)) || FAILED(hr = MFSetAttributeRatio(output_type->Get(), MF_MT_PIXEL_ASPECT_RATIO, par_numerator, par_denominator))) [[unlikely]] { @@ -1270,7 +1286,6 @@ void MediaCaptureMF::ClearState() m_video_sample_duration = 0; m_audio_sample_duration = 0; - m_frame_rate_numerator = 0; m_video_yuv_transform.Reset(); m_video_yuv_sample.Reset(); @@ -1325,14 +1340,13 @@ bool MediaCaptureMF::SendFrame(const PendingFrame& pf, Error* error) return false; } - const LONGLONG timestamp = static_cast(pf.pts) * m_video_sample_duration; - if (FAILED(hr = sample->SetSampleTime(timestamp))) [[unlikely]] + if (FAILED(hr = sample->SetSampleTime(ConvertPTSToTimestamp(pf.pts, m_video_sample_duration)))) [[unlikely]] { Error::SetHResult(error, "SetSampleTime() failed: ", hr); return false; } - if (FAILED(hr = sample->SetSampleDuration(m_video_sample_duration))) [[unlikely]] + if (FAILED(hr = sample->SetSampleDuration(static_cast(m_video_sample_duration)))) [[unlikely]] { Error::SetHResult(error, "SetSampleDuration() failed: ", hr); return false; @@ -1746,15 +1760,15 @@ bool MediaCaptureMF::ProcessAudioPackets(s64 video_pts, Error* error) return false; } - const LONGLONG timestamp = static_cast(m_next_audio_pts) * m_audio_sample_duration; - if (FAILED(hr = sample->SetSampleTime(timestamp))) [[unlikely]] + if (FAILED(hr = sample->SetSampleTime(ConvertPTSToTimestamp(m_next_audio_pts, m_audio_sample_duration)))) + [[unlikely]] { Error::SetHResult(error, "Audio SetSampleTime() failed: ", hr); return false; } - const LONGLONG duration = static_cast(contig_frames) * m_audio_sample_duration; - if (FAILED(hr = sample->SetSampleDuration(duration))) [[unlikely]] + if (FAILED(hr = sample->SetSampleDuration(ConvertFramesToDuration(contig_frames, m_audio_sample_duration)))) + [[unlikely]] { Error::SetHResult(error, "Audio SetSampleDuration() failed: ", hr); return false;