ZaStoGram/TMessagesProj/jni/voip/tgcalls/group/GroupFrameTransformer.h
2026-09-24 16:17:30 +04:00

156 lines
6.2 KiB
C++

#ifndef TGCALLS_GROUP_FRAME_TRANSFORMER_H
#define TGCALLS_GROUP_FRAME_TRANSFORMER_H
#include <cstdint>
#include <functional>
#include <map>
#include <memory>
#include <utility>
#include <vector>
#include "api/array_view.h"
#include "api/frame_transformer_interface.h"
#include "rtc_base/synchronization/mutex.h"
namespace tgcalls {
// Encrypt/decrypt callback supplied by the application through
// GroupInstanceDescriptor::e2eEncryptDecrypt:
// (data, userId, isEncrypt, plaintextPrefixLength) -> transformed data.
// An empty return means failure; the frame is then dropped.
using GroupEncryptDecryptFunction =
std::function<std::vector<uint8_t>(std::vector<uint8_t> const &, int64_t, bool, int32_t)>;
// Holds the local audio level/speech state stamped into every outgoing Opus
// frame. Written from the levels timer, read from the encoder queue.
class AudioLevelAndSpeechHolder {
public:
AudioLevelAndSpeechHolder() {
}
void set(uint8_t audioLevel, bool hasSpeech) {
webrtc::MutexLock lock(&_mutex);
_audioLevel = audioLevel;
_hasSpeech = hasSpeech;
}
std::pair<uint8_t, bool> get() {
webrtc::MutexLock lock(&_mutex);
return std::make_pair(_audioLevel, _hasSpeech);
}
private:
webrtc::Mutex _mutex;
uint8_t _audioLevel = 0;
bool _hasSpeech = false;
};
enum class FrameTransformerPayloadType {
Unknown,
Opus,
H264,
VP8
};
// Returns the frame with every 3-byte NAL start code rewritten to 4 bytes (the
// receiving WebRTC does the same, which would otherwise shift the ciphertext),
// and reports through `headerSize` how many leading bytes must stay in the
// clear for the SFU to parse the packet.
std::vector<uint8_t> calculateH264FramePlaintextHeaderSize(
rtc::ArrayView<const uint8_t> frame, uint32_t &headerSize);
// VP8: 10 plaintext bytes for a key frame (full uncompressed header), 1 for a
// delta frame.
std::vector<uint8_t> calculateVp8FramePlaintextHeaderSize(
rtc::ArrayView<const uint8_t> frame, uint32_t &headerSize);
// H264/H265 ciphertext must not contain a 3- or 4-byte NAL start code, or the
// packetizer misparses the frame and the receiver cannot decrypt it. Always
// true for non-H264 payload types.
bool ValidateEncryptedFrame(FrameTransformerPayloadType payloadType,
rtc::ArrayView<uint8_t> frame,
int plaintextPrefix);
// The four payload transforms, factored out of FrameTransformer so callers that
// are not a FrameTransformerInterface (the reference engine's unsignaled-SSRC
// discovery tap) reuse the exact same byte layout. Each returns an empty vector
// on failure; the caller drops the frame.
// Appends [0x01][(hasSpeech << 7) | (audioLevel & 0x7f)] and encrypts the whole
// buffer with plaintextPrefixLength 0.
std::vector<uint8_t> encryptGroupAudioFrame(
GroupEncryptDecryptFunction const &transform,
int64_t userId,
rtc::ArrayView<const uint8_t> frame,
uint8_t audioLevel,
bool hasSpeech);
// Computes the codec-specific plaintext prefix, encrypts, and retries up to 4
// times while ValidateEncryptedFrame rejects the ciphertext.
std::vector<uint8_t> encryptGroupVideoFrame(
GroupEncryptDecryptFunction const &transform,
int64_t userId,
FrameTransformerPayloadType payloadType,
rtc::ArrayView<const uint8_t> frame);
// Decrypts and strips the trailer. When the extension flag bit is set, reports
// the level/speech through the out-parameters and strips two bytes; when it is
// clear, strips one and leaves them alone. `hadExtension` reports which happened,
// so a caller can mirror CustomImpl and only publish a level when one was
// actually carried. Every out-parameter may be null.
std::vector<uint8_t> decryptGroupAudioFrame(
GroupEncryptDecryptFunction const &transform,
int64_t userId,
rtc::ArrayView<const uint8_t> frame,
uint8_t *audioLevel,
bool *hasSpeech,
bool *hadExtension);
std::vector<uint8_t> decryptGroupVideoFrame(
GroupEncryptDecryptFunction const &transform,
int64_t userId,
rtc::ArrayView<const uint8_t> frame);
class FrameTransformer : public webrtc::FrameTransformerInterface {
public:
FrameTransformer(bool isEncryptor,
GroupEncryptDecryptFunction transform,
int64_t userId,
std::map<int32_t, FrameTransformerPayloadType> const &payloadTypeMapping,
std::function<std::pair<uint8_t, bool>()> getAudioLevelAndSpeech,
std::function<void(uint8_t, bool)> setAudioLevelAndSpeech);
// Variant whose userId is resolved per frame from its SSRC. The reference
// engine adds a recvonly transceiver before it knows the sender's user id
// (the requestMediaChannelDescriptions response lands later), so the id
// cannot be fixed at construction.
FrameTransformer(bool isEncryptor,
GroupEncryptDecryptFunction transform,
std::function<int64_t(uint32_t ssrc)> userIdForSsrc,
std::map<int32_t, FrameTransformerPayloadType> const &payloadTypeMapping,
std::function<std::pair<uint8_t, bool>()> getAudioLevelAndSpeech,
std::function<void(uint8_t, bool)> setAudioLevelAndSpeech);
void RegisterTransformedFrameCallback(
rtc::scoped_refptr<webrtc::TransformedFrameCallback> callback) override;
void RegisterTransformedFrameSinkCallback(
rtc::scoped_refptr<webrtc::TransformedFrameCallback> callback, uint32_t ssrc) override;
void UnregisterTransformedFrameSinkCallback(uint32_t ssrc) override;
void Transform(std::unique_ptr<webrtc::TransformableFrameInterface> frame) override;
private:
bool _isEncryptor = false;
GroupEncryptDecryptFunction _transform;
int64_t _userId = 0;
std::function<int64_t(uint32_t)> _userIdForSsrc;
std::map<int32_t, FrameTransformerPayloadType> _payloadTypeMapping;
std::function<std::pair<uint8_t, bool>()> _getAudioLevelAndSpeech;
std::function<void(uint8_t, bool)> _setAudioLevelAndSpeech;
webrtc::Mutex _mutex;
rtc::scoped_refptr<webrtc::TransformedFrameCallback> _sinkCallback;
std::map<uint32_t, rtc::scoped_refptr<webrtc::TransformedFrameCallback>> _sinkCallbackBySsrc;
};
} // namespace tgcalls
#endif