Add support for the Absolute Capture Timestamp extension to TransformableAudioFrameInterface

Bug: chromium:391114797
Change-Id: Iad09ed3b509ce7874c44cd17c1e87b6945b14b07
Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/377121
Reviewed-by: Harald Alvestrand <hta@webrtc.org>
Reviewed-by: Jakob Ivarsson‎ <jakobi@webrtc.org>
Commit-Queue: Guido Urdaneta <guidou@webrtc.org>
Cr-Commit-Position: refs/heads/main@{#43893}
This commit is contained in:
Guido Urdaneta 2025-02-13 21:21:30 +01:00 committed by WebRTC LUCI CQ
parent 1e30ce21aa
commit e47e4677ee
6 changed files with 98 additions and 1 deletions

View File

@ -410,6 +410,7 @@ rtc_source_set("frame_transformer_interface") {
":scoped_refptr", ":scoped_refptr",
"../rtc_base:refcount", "../rtc_base:refcount",
"../rtc_base/system:rtc_export", "../rtc_base/system:rtc_export",
"units:time_delta",
"units:timestamp", "units:timestamp",
"video:encoded_frame", "video:encoded_frame",
"video:video_frame_metadata", "video:video_frame_metadata",

View File

@ -19,6 +19,7 @@
#include "api/array_view.h" #include "api/array_view.h"
#include "api/ref_count.h" #include "api/ref_count.h"
#include "api/scoped_refptr.h" #include "api/scoped_refptr.h"
#include "api/units/time_delta.h"
#include "api/units/timestamp.h" #include "api/units/timestamp.h"
#include "api/video/video_frame_metadata.h" #include "api/video/video_frame_metadata.h"
#include "rtc_base/system/rtc_export.h" #include "rtc_base/system/rtc_export.h"
@ -99,6 +100,7 @@ class TransformableAudioFrameInterface : public TransformableFrameInterface {
virtual const std::optional<uint16_t> SequenceNumber() const = 0; virtual const std::optional<uint16_t> SequenceNumber() const = 0;
// TODO(crbug.com/391114797): Delete this function.
virtual std::optional<uint64_t> AbsoluteCaptureTimestamp() const = 0; virtual std::optional<uint64_t> AbsoluteCaptureTimestamp() const = 0;
enum class FrameType { kEmptyFrame, kAudioFrameSpeech, kAudioFrameCN }; enum class FrameType { kEmptyFrame, kAudioFrameSpeech, kAudioFrameCN };
@ -115,6 +117,21 @@ class TransformableAudioFrameInterface : public TransformableFrameInterface {
// Timestamp at which the packet has been first seen on the network interface. // Timestamp at which the packet has been first seen on the network interface.
// Only defined for received audio packet. // Only defined for received audio packet.
virtual std::optional<Timestamp> ReceiveTime() const = 0; virtual std::optional<Timestamp> ReceiveTime() const = 0;
// Timestamp at which the frame was captured in the capturer system.
// The timestamp is expressed in the capturer system's clock relative to the
// NTP epoch (January 1st 1970 00:00 UTC)
// Accessible only if the absolute capture timestamp header extension is
// enabled.
virtual std::optional<Timestamp> CaptureTime() const = 0;
// Offset between the sender system's clock and the capturer system's clock.
// Can be used to express the capture time in the local system's clock as
// long as the local system can determine the offset between its local clock
// and the sender system's clock.
// Accessible only if the absolute capture timestamp header extension is
// enabled.
virtual std::optional<TimeDelta> SenderCaptureTimeOffset() const = 0;
}; };
// Objects implement this interface to be notified with the transformed frame. // Objects implement this interface to be notified with the transformed frame.

View File

@ -56,6 +56,11 @@ class MockTransformableAudioFrame : public TransformableAudioFrameInterface {
MOCK_METHOD(std::optional<uint8_t>, AudioLevel, (), (const, override)); MOCK_METHOD(std::optional<uint8_t>, AudioLevel, (), (const, override));
MOCK_METHOD(std::optional<Timestamp>, ReceiveTime, (), (const, override)); MOCK_METHOD(std::optional<Timestamp>, ReceiveTime, (), (const, override));
MOCK_METHOD(std::optional<Timestamp>, CaptureTime, (), (const, override));
MOCK_METHOD(std::optional<TimeDelta>,
SenderCaptureTimeOffset,
(),
(const, override));
}; };
} // namespace webrtc } // namespace webrtc

View File

@ -22,9 +22,11 @@
#include "api/scoped_refptr.h" #include "api/scoped_refptr.h"
#include "api/sequence_checker.h" #include "api/sequence_checker.h"
#include "api/task_queue/task_queue_base.h" #include "api/task_queue/task_queue_base.h"
#include "api/units/time_delta.h"
#include "api/units/timestamp.h" #include "api/units/timestamp.h"
#include "rtc_base/buffer.h" #include "rtc_base/buffer.h"
#include "rtc_base/string_encode.h" #include "rtc_base/string_encode.h"
#include "system_wrappers/include/ntp_time.h"
namespace webrtc { namespace webrtc {
@ -96,6 +98,25 @@ class TransformableIncomingAudioFrame
: std::optional<Timestamp>(receive_time_); : std::optional<Timestamp>(receive_time_);
} }
std::optional<Timestamp> CaptureTime() const override {
if (header_.extension.absolute_capture_time) {
return Timestamp::Micros(UQ32x32ToInt64Us(
header_.extension.absolute_capture_time->absolute_capture_timestamp));
}
return std::nullopt;
}
std::optional<TimeDelta> SenderCaptureTimeOffset() const override {
if (header_.extension.absolute_capture_time &&
header_.extension.absolute_capture_time
->estimated_capture_clock_offset) {
return TimeDelta::Micros(
UQ32x32ToInt64Us(*header_.extension.absolute_capture_time
->estimated_capture_clock_offset));
}
return std::nullopt;
}
private: private:
rtc::Buffer payload_; rtc::Buffer payload_;
RTPHeader header_; RTPHeader header_;

View File

@ -24,6 +24,7 @@
#include "api/test/mock_transformable_audio_frame.h" #include "api/test/mock_transformable_audio_frame.h"
#include "api/units/timestamp.h" #include "api/units/timestamp.h"
#include "rtc_base/thread.h" #include "rtc_base/thread.h"
#include "system_wrappers/include/ntp_time.h"
#include "test/gmock.h" #include "test/gmock.h"
#include "test/gtest.h" #include "test/gtest.h"
@ -193,7 +194,7 @@ TEST(ChannelReceiveFrameTransformerDelegateTest,
} }
TEST(ChannelReceiveFrameTransformerDelegateTest, TEST(ChannelReceiveFrameTransformerDelegateTest,
AudioLevelAbsentWithoutExtension) { AudioLevelAndCaptureTimeAbsentWithoutExtension) {
rtc::AutoThread main_thread; rtc::AutoThread main_thread;
rtc::scoped_refptr<MockFrameTransformer> mock_frame_transformer = rtc::scoped_refptr<MockFrameTransformer> mock_frame_transformer =
rtc::make_ref_counted<NiceMock<MockFrameTransformer>>(); rtc::make_ref_counted<NiceMock<MockFrameTransformer>>();
@ -223,6 +224,8 @@ TEST(ChannelReceiveFrameTransformerDelegateTest,
auto* audio_frame = auto* audio_frame =
static_cast<TransformableAudioFrameInterface*>(frame.get()); static_cast<TransformableAudioFrameInterface*>(frame.get());
EXPECT_FALSE(audio_frame->AudioLevel()); EXPECT_FALSE(audio_frame->AudioLevel());
EXPECT_FALSE(audio_frame->CaptureTime());
EXPECT_FALSE(audio_frame->SenderCaptureTimeOffset());
EXPECT_EQ(audio_frame->Type(), EXPECT_EQ(audio_frame->Type(),
TransformableAudioFrameInterface::FrameType::kAudioFrameCN); TransformableAudioFrameInterface::FrameType::kAudioFrameCN);
} }
@ -265,5 +268,48 @@ TEST(ChannelReceiveFrameTransformerDelegateTest,
TransformableAudioFrameInterface::FrameType::kAudioFrameSpeech); TransformableAudioFrameInterface::FrameType::kAudioFrameSpeech);
} }
TEST(ChannelReceiveFrameTransformerDelegateTest,
CaptureTimePresentWithExtension) {
rtc::AutoThread main_thread;
rtc::scoped_refptr<MockFrameTransformer> mock_frame_transformer =
rtc::make_ref_counted<NiceMock<MockFrameTransformer>>();
rtc::scoped_refptr<ChannelReceiveFrameTransformerDelegate> delegate =
rtc::make_ref_counted<ChannelReceiveFrameTransformerDelegate>(
/*receive_frame_callback=*/nullptr, mock_frame_transformer,
rtc::Thread::Current());
rtc::scoped_refptr<TransformedFrameCallback> callback;
EXPECT_CALL(*mock_frame_transformer, RegisterTransformedFrameCallback)
.WillOnce(SaveArg<0>(&callback));
delegate->Init();
ASSERT_TRUE(callback);
const uint8_t data[] = {1, 2, 3, 4};
rtc::ArrayView<const uint8_t> packet(data, sizeof(data));
Timestamp capture_time = Timestamp::Millis(1234);
TimeDelta sender_capture_time_offset = TimeDelta::Millis(56);
AbsoluteCaptureTime absolute_capture_time = {
.absolute_capture_timestamp = Int64MsToUQ32x32(capture_time.ms()),
.estimated_capture_clock_offset =
Int64MsToUQ32x32(sender_capture_time_offset.ms())};
RTPHeader header;
header.extension.absolute_capture_time = absolute_capture_time;
std::unique_ptr<TransformableFrameInterface> frame;
ON_CALL(*mock_frame_transformer, Transform)
.WillByDefault(
[&](std::unique_ptr<TransformableFrameInterface> transform_frame) {
frame = std::move(transform_frame);
});
delegate->Transform(packet, header, /*ssrc=*/1111, /*mimeType=*/"audio/opus",
kFakeReceiveTimestamp);
EXPECT_TRUE(frame);
auto* audio_frame =
static_cast<TransformableAudioFrameInterface*>(frame.get());
EXPECT_EQ(*audio_frame->CaptureTime(), capture_time);
EXPECT_EQ(*audio_frame->SenderCaptureTimeOffset(),
sender_capture_time_offset);
}
} // namespace } // namespace
} // namespace webrtc } // namespace webrtc

View File

@ -13,6 +13,9 @@
#include <utility> #include <utility>
#include <vector> #include <vector>
#include "api/units/time_delta.h"
#include "api/units/timestamp.h"
namespace webrtc { namespace webrtc {
namespace { namespace {
@ -110,6 +113,10 @@ class TransformableOutgoingAudioFrame
} }
std::optional<Timestamp> ReceiveTime() const override { return std::nullopt; } std::optional<Timestamp> ReceiveTime() const override { return std::nullopt; }
std::optional<Timestamp> CaptureTime() const override { return std::nullopt; }
std::optional<TimeDelta> SenderCaptureTimeOffset() const override {
return std::nullopt;
}
private: private:
AudioFrameType frame_type_; AudioFrameType frame_type_;