diff --git a/video/ffmpeg_decode.cpp b/video/ffmpeg_decode.cpp index 64b3c888..5960f8cd 100644 --- a/video/ffmpeg_decode.cpp +++ b/video/ffmpeg_decode.cpp @@ -32,7 +32,6 @@ #include "thread_group.hpp" #include "global_managers.hpp" #include "thread_priority.hpp" -#include "ffmpeg_raw_packet.hpp" #include "timer.hpp" #include #include @@ -600,15 +599,8 @@ struct VideoDecoder::Impl double smooth_pts = 0.0; DemuxerIOInterface *io_interface = nullptr; - struct CodecParameters - { - PacketHeader header; - CodecParams params; - } raw_codec_params = {}; + pyro_codec_parameters pyro_codec = {}; bool has_observed_keyframe = false; - bool find_raw_codec_parameters(); - bool read_raw_skip(uint32_t size) const; - int read_frame(AVPacket *av_pkt); }; @@ -904,21 +896,21 @@ bool VideoDecoder::Impl::init_audio_decoder() } else { - switch (raw_codec_params.params.audio_codec) + switch (pyro_codec.audio_codec) { - case AudioCodec::Opus: + case PYRO_AUDIO_CODEC_OPUS: codec = avcodec_find_decoder(AV_CODEC_ID_OPUS); break; - case AudioCodec::AAC: + case PYRO_AUDIO_CODEC_AAC: codec = avcodec_find_decoder(AV_CODEC_ID_AAC); break; - case AudioCodec::S16LE: + case PYRO_AUDIO_CODEC_RAW_S16LE: codec = avcodec_find_decoder(AV_CODEC_ID_PCM_S16LE); break; - case AudioCodec::None: + case PYRO_AUDIO_CODEC_NONE: return true; default: @@ -952,14 +944,14 @@ bool VideoDecoder::Impl::init_audio_decoder() } else { - audio.av_ctx->sample_rate = raw_codec_params.params.rate; - if (raw_codec_params.params.channels == 2) + audio.av_ctx->sample_rate = pyro_codec.rate; + if (pyro_codec.channels == 2) audio.av_ctx->ch_layout = AV_CHANNEL_LAYOUT_STEREO; - else if (raw_codec_params.params.channels == 1) + else if (pyro_codec.channels == 1) audio.av_ctx->ch_layout = AV_CHANNEL_LAYOUT_MONO; else { - LOGE("Unexpected audio channel count %u.\n", raw_codec_params.params.channels); + LOGE("Unexpected audio channel count %u.\n", pyro_codec.channels); return false; } } @@ -1049,8 +1041,8 @@ bool VideoDecoder::Impl::init_video_decoder_post_device() else { AVRational q; - q.num = raw_codec_params.params.frame_rate_num; - q.den = raw_codec_params.params.frame_rate_den; + q.num = pyro_codec.frame_rate_num; + q.den = pyro_codec.frame_rate_den; fps = av_q2d(q); } @@ -1094,17 +1086,17 @@ bool VideoDecoder::Impl::init_video_decoder_pre_device() } else if (io_interface) { - switch (raw_codec_params.params.video_codec) + switch (pyro_codec.video_codec) { - case VideoCodec::H264: + case PYRO_VIDEO_CODEC_H264: codec = avcodec_find_decoder(AV_CODEC_ID_H264); break; - case VideoCodec::H265: + case PYRO_VIDEO_CODEC_H265: codec = avcodec_find_decoder(AV_CODEC_ID_H265); break; - case VideoCodec::AV1: + case PYRO_VIDEO_CODEC_AV1: codec = avcodec_find_decoder(AV_CODEC_ID_AV1); break; @@ -1138,10 +1130,12 @@ bool VideoDecoder::Impl::init_video_decoder_pre_device() } else { - video.av_ctx->width = raw_codec_params.params.width; - video.av_ctx->height = raw_codec_params.params.height; - video.av_ctx->framerate.num = raw_codec_params.params.frame_rate_num; - video.av_ctx->framerate.den = raw_codec_params.params.frame_rate_den; + video.av_ctx->width = pyro_codec.width; + video.av_ctx->height = pyro_codec.height; + video.av_ctx->framerate.num = pyro_codec.frame_rate_num; + video.av_ctx->framerate.den = pyro_codec.frame_rate_den; + // Packet loss is expected, and we'd rather have something on screen than nothing. + video.av_ctx->flags |= AV_CODEC_FLAG_OUTPUT_CORRUPT; } video.av_ctx->opaque = &hw; @@ -1158,46 +1152,6 @@ unsigned VideoDecoder::Impl::get_height() const return video.av_ctx->height; } -bool VideoDecoder::Impl::read_raw_skip(uint32_t size) const -{ - uint8_t dummy[1024]; - for (uint32_t i = 0; i < size; i += sizeof(dummy)) - if (!io_interface->read(dummy, std::min(size - i, sizeof(dummy)))) - return false; - return true; -} - -bool VideoDecoder::Impl::find_raw_codec_parameters() -{ - for (;;) - { - if (!io_interface->read(&raw_codec_params.header, sizeof(raw_codec_params.header))) - return false; - - if (raw_codec_params.header.header_magic != PyroMagic) - { - LOGE("Invalid Pyro magic.\n"); - return false; - } - - if (raw_codec_params.header.endpoint == Endpoint::CodecParam) - { - if (raw_codec_params.header.payload_size != sizeof(raw_codec_params.params)) - { - LOGE("Invalid size for CodecParameters.\n"); - return false; - } - - if (!io_interface->read(&raw_codec_params.params, sizeof(raw_codec_params.params))) - return false; - - return true; - } - else if (!read_raw_skip(raw_codec_params.header.payload_size)) - return false; - } -} - bool VideoDecoder::Impl::init(Audio::Mixer *mixer_, const char *path, const DecodeOptions &opts_) { mixer = mixer_; @@ -1217,10 +1171,14 @@ bool VideoDecoder::Impl::init(Audio::Mixer *mixer_, const char *path, const Deco return false; } } - else if (!find_raw_codec_parameters()) + else { - LOGE("Failed to get raw codec parameters.\n"); - return false; + pyro_codec = io_interface->get_codec_parameters(); + if (pyro_codec.video_codec == PYRO_VIDEO_CODEC_NONE) + { + LOGE("Failed to get raw codec parameters.\n"); + return false; + } } if (!init_video_decoder_pre_device()) @@ -1843,48 +1801,20 @@ int VideoDecoder::Impl::read_frame(AVPacket *pkt) { av_packet_unref(pkt); - PacketHeader header; - for (;;) + do { - if (!io_interface->read(&header, sizeof(header))) - return false; - if (header.header_magic != PyroMagic) - { - LOGE("Invalid Pyro magic.\n"); - return AVERROR_EOF; - } - - if (header.endpoint != Endpoint::VideoPacket && header.endpoint != Endpoint::AudioPacket) - { - if (!read_raw_skip(header.payload_size)) - return AVERROR_EOF; - continue; - } - - PayloadHeader pkt_header; - if (header.payload_size < sizeof(pkt_header)) - { - LOGE("Invalid packet header.\n"); - return AVERROR_EOF; - } - - if (!io_interface->read(&pkt_header, sizeof(pkt_header))) + if (!io_interface->wait_next_packet()) return AVERROR_EOF; - header.payload_size -= sizeof(pkt_header); - - pkt->size = header.payload_size; - - if (av_new_packet(pkt, pkt->size) < 0) - return AVERROR_EOF; - - if (!io_interface->read(pkt->data, header.payload_size)) + if (av_new_packet(pkt, int(io_interface->get_size())) < 0) return AVERROR_EOF; - pkt->pts = pkt_header.pts; - pkt->dts = pkt_header.dts; + memcpy(pkt->data, io_interface->get_data(), pkt->size); + auto header = io_interface->get_payload_header(); + pkt->pts = header.pts_lo | (int64_t(header.pts_hi) << 32); + pkt->dts = pkt->pts - header.dts_delta; - if (pkt_header.flags & PAYLOAD_KEY_FRAME_BIT) + if ((header.encoded & PYRO_PAYLOAD_KEY_FRAME_BIT) != 0) { av_pkt->flags = AV_PKT_FLAG_KEY; has_observed_keyframe = true; @@ -1892,14 +1822,8 @@ int VideoDecoder::Impl::read_frame(AVPacket *pkt) else av_pkt->flags = 0; - if (header.endpoint == Endpoint::VideoPacket) - pkt->stream_index = 0; - else if (header.endpoint == Endpoint::AudioPacket) - pkt->stream_index = 1; - - if (has_observed_keyframe) - break; - } + pkt->stream_index = (header.encoded & PYRO_PAYLOAD_STREAM_TYPE_BIT) != 0 ? 1 : 0; + } while (!has_observed_keyframe); return 0; } diff --git a/video/ffmpeg_decode.hpp b/video/ffmpeg_decode.hpp index 2ad6aee2..7bddf12b 100644 --- a/video/ffmpeg_decode.hpp +++ b/video/ffmpeg_decode.hpp @@ -4,6 +4,7 @@ #include "image.hpp" #include "semaphore.hpp" #include "slangmosh_decode_iface.hpp" +#include "pyro_protocol.h" namespace Granite { @@ -25,7 +26,11 @@ class DemuxerIOInterface { public: virtual ~DemuxerIOInterface() = default; - virtual bool read(void *data, size_t size) = 0; + virtual pyro_codec_parameters get_codec_parameters() = 0; + virtual bool wait_next_packet() = 0; + virtual const void *get_data() = 0; + virtual size_t get_size() = 0; + virtual pyro_payload_header get_payload_header() = 0; }; class VideoDecoder diff --git a/video/ffmpeg_encode.cpp b/video/ffmpeg_encode.cpp index 720031f8..c7444e0a 100644 --- a/video/ffmpeg_encode.cpp +++ b/video/ffmpeg_encode.cpp @@ -24,7 +24,6 @@ #include "ffmpeg_encode.hpp" #include "ffmpeg_hw_device.hpp" -#include "ffmpeg_raw_packet.hpp" #include "logging.hpp" #include "math.hpp" #include "muglm/muglm_impl.hpp" @@ -165,14 +164,7 @@ struct VideoEncoder::Impl #endif void submit_process_rgb_readback(Vulkan::CommandBufferHandle &cmd, YCbCrPipelineData &pipeline); - struct PyroHeader - { - PacketHeader param_header; - CodecParams codec_params; - PacketHeader payload_packet_header; - PayloadHeader payload_header; - } raw_header = {}; - Util::DynamicArray packet_buffer; + pyro_codec_parameters pyro_codec = {}; }; static void free_av_objects(CodecStream &stream) @@ -637,30 +629,22 @@ bool VideoEncoder::Impl::drain_packets(CodecStream &stream) } else if (mux_stream_callback) { - size_t write_size = sizeof(raw_header) + stream.av_pkt->size; - packet_buffer.reserve(write_size); - raw_header.payload_header.pts = stream.av_pkt->pts; - raw_header.payload_header.dts = stream.av_pkt->dts; - raw_header.payload_header.flags = 0; - raw_header.payload_packet_header.payload_size = - stream.av_pkt->size + sizeof(raw_header.payload_header); - if (&stream == &video) { - if (stream.av_pkt->flags & AV_PKT_FLAG_KEY) - raw_header.payload_header.flags |= PAYLOAD_KEY_FRAME_BIT; - raw_header.payload_packet_header.endpoint = Endpoint::VideoPacket; + // Avoid negative values in the beginning by biasing. + mux_stream_callback->write_video_packet( + stream.av_pkt->pts, + stream.av_pkt->dts, + stream.av_pkt->data, stream.av_pkt->size, + (stream.av_pkt->flags & AV_PKT_FLAG_KEY) != 0); } else if (&stream == &audio) - raw_header.payload_packet_header.endpoint = Endpoint::AudioPacket; - else - raw_header.payload_packet_header.endpoint = Endpoint::None; - - memcpy(packet_buffer.data(), &raw_header, sizeof(raw_header)); - memcpy(packet_buffer.data() + sizeof(raw_header), - stream.av_pkt->data, stream.av_pkt->size); - - mux_stream_callback->write_stream(packet_buffer.data(), write_size); + { + mux_stream_callback->write_audio_packet( + stream.av_pkt->pts, + stream.av_pkt->dts, + stream.av_pkt->data, stream.av_pkt->size); + } } } @@ -696,12 +680,12 @@ bool VideoEncoder::Impl::init_audio_codec() codec_id = AV_CODEC_ID_PCM_S16LE; sample_fmt = AV_SAMPLE_FMT_S16; - raw_header.codec_params.audio_codec = AudioCodec::S16LE; - raw_header.codec_params.channels = 2; + pyro_codec.audio_codec = PYRO_AUDIO_CODEC_RAW_S16LE; + pyro_codec.channels = 2; if (options.realtime) - raw_header.codec_params.rate = uint32_t(audio_stream->get_sample_rate()); + pyro_codec.rate = uint32_t(audio_stream->get_sample_rate()); else - raw_header.codec_params.rate = uint32_t(audio_source->get_sample_rate()); + pyro_codec.rate = uint32_t(audio_source->get_sample_rate()); } else { @@ -802,7 +786,7 @@ bool VideoEncoder::Impl::init_audio_codec() { samples_per_tick = audio.av_ctx->frame_size; if (samples_per_tick == 0) - samples_per_tick = 512; // About 10ms packets is fine. + samples_per_tick = 256; // About 5ms packets is fine and fits into one pyro UDP packet. } audio.av_frame = alloc_audio_frame(audio.av_ctx->sample_fmt, audio.av_ctx->ch_layout, @@ -1028,22 +1012,22 @@ bool VideoEncoder::Impl::init_video_codec() switch (id) { case AV_CODEC_ID_H264: - return VideoCodec::H264; + return PYRO_VIDEO_CODEC_H264; case AV_CODEC_ID_H265: - return VideoCodec::H265; + return PYRO_VIDEO_CODEC_H265; case AV_CODEC_ID_AV1: - return VideoCodec::AV1; + return PYRO_VIDEO_CODEC_AV1; default: LOGW("Unknown video codec %d.\n", id); - return VideoCodec::None; + return PYRO_VIDEO_CODEC_NONE; } }; - raw_header.codec_params.video_codec = translate_codec_id(video.av_ctx->codec_id); - raw_header.codec_params.width = video.av_ctx->width; - raw_header.codec_params.height = video.av_ctx->height; - raw_header.codec_params.frame_rate_num = video.av_ctx->framerate.num; - raw_header.codec_params.frame_rate_den = video.av_ctx->framerate.den; + pyro_codec.video_codec = translate_codec_id(video.av_ctx->codec_id); + pyro_codec.width = video.av_ctx->width; + pyro_codec.height = video.av_ctx->height; + pyro_codec.frame_rate_num = video.av_ctx->framerate.num; + pyro_codec.frame_rate_den = video.av_ctx->framerate.den; video.av_pkt = av_packet_alloc(); if (!video.av_pkt) @@ -1106,11 +1090,6 @@ bool VideoEncoder::Impl::init(Vulkan::Device *device_, const char *path, const O } } - raw_header.param_header.header_magic = PyroMagic; - raw_header.param_header.endpoint = Endpoint::CodecParam; - raw_header.param_header.payload_size = sizeof(raw_header.codec_params); - raw_header.payload_packet_header.header_magic = PyroMagic; - if (!init_video_codec()) { cleanup_format_context(); @@ -1126,6 +1105,9 @@ bool VideoEncoder::Impl::init(Vulkan::Device *device_, const char *path, const O } } + if (mux_stream_callback) + mux_stream_callback->set_codec_parameters(pyro_codec); + if (av_format_ctx) av_dump_format(av_format_ctx, 0, path, 1); if (av_format_ctx_local) diff --git a/video/ffmpeg_encode.hpp b/video/ffmpeg_encode.hpp index 38143b04..a86fd453 100644 --- a/video/ffmpeg_encode.hpp +++ b/video/ffmpeg_encode.hpp @@ -25,6 +25,7 @@ #include "device.hpp" #include "image.hpp" #include "slangmosh_encode_iface.hpp" +#include "pyro_protocol.h" namespace Granite { @@ -38,7 +39,9 @@ class MuxStreamCallback { public: virtual ~MuxStreamCallback() = default; - virtual bool write_stream(const void *data, size_t size) = 0; + virtual void set_codec_parameters(const pyro_codec_parameters &codec) = 0; + virtual void write_video_packet(int64_t pts, int64_t dts, const void *data, size_t size, bool is_key_frame) = 0; + virtual void write_audio_packet(int64_t pts, int64_t dts, const void *data, size_t size) = 0; }; class VideoEncoder diff --git a/video/ffmpeg_raw_packet.hpp b/video/ffmpeg_raw_packet.hpp deleted file mode 100644 index 3013ce5b..00000000 --- a/video/ffmpeg_raw_packet.hpp +++ /dev/null @@ -1,104 +0,0 @@ -/* Copyright (c) 2017-2023 Hans-Kristian Arntzen - * - * Permission is hereby granted, free of charge, to any person obtaining - * a copy of this software and associated documentation files (the - * "Software"), to deal in the Software without restriction, including - * without limitation the rights to use, copy, modify, merge, publish, - * distribute, sublicense, and/or sell copies of the Software, and to - * permit persons to whom the Software is furnished to do so, subject to - * the following conditions: - * - * The above copyright notice and this permission notice shall be - * included in all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. - * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY - * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, - * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE - * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - */ - -#pragma once -#include - -// Ad-hoc raw packet format which bypasses misc issues with existing realtime muxers. -// RTP: Needs multiple muxers, highly non-trivial to mux them together. Designed for multiple ports over UDP. -// MPEG2TS: Unacceptable muxing delay (>80ms) when muxing audio and video together. -// This is supposed to be suitable to hammer through raw packets over a stream connection -// with no reordering or anything. - -namespace Granite -{ -enum class VideoCodec : uint32_t -{ - None = 0, - H264 = 1, - H265 = 2, - AV1 = 3 -}; - -enum class AudioCodec : uint16_t -{ - None = 0, - AAC = 1, - Opus = 2, - S16LE = 3, - Count -}; - -enum class Endpoint : uint32_t -{ - None = 0, - CodecParam = 1, - VideoPacket = 3, - AudioPacket = 4, - Count -}; - -static constexpr uint64_t PyroMagic = - (uint64_t('P') << 56) | - (uint64_t('Y') << 48) | - (uint64_t('R') << 40) | - (uint64_t('O') << 32) | - (0xdeull << 24) | - (0xadull << 16) | - (uint64_t('V') << 8) | - (uint64_t('1') << 0); - -struct PacketHeader -{ - uint64_t header_magic; - Endpoint endpoint; - uint32_t payload_size; -}; -static_assert(sizeof(PacketHeader) == 16, "Unexpected size for PacketHeader."); - -struct CodecParams -{ - VideoCodec video_codec; - AudioCodec audio_codec; - uint16_t frame_rate_num; - uint16_t frame_rate_den; - uint16_t width; - uint16_t height; - uint32_t channels; - uint32_t rate; -}; -static_assert(sizeof(CodecParams) == 24, "Unexpected size for CodecParams."); - -enum PayloadFlagsBits : uint64_t -{ - PAYLOAD_KEY_FRAME_BIT = 1 << 0, -}; -using PayloadFlags = uint64_t; - -struct PayloadHeader -{ - int64_t pts; // Linear TS to be passed into AVPacket - int64_t dts; // Linear TS to be passed into AVPacket - PayloadFlags flags; -}; -static_assert(sizeof(PayloadHeader) == 24, "Unexpected size for PayloadHeader."); -} diff --git a/video/pyro_protocol.h b/video/pyro_protocol.h new file mode 100644 index 00000000..7085feac --- /dev/null +++ b/video/pyro_protocol.h @@ -0,0 +1,145 @@ +#ifndef PYRO_PROTOCOL_H_ +#define PYRO_PROTOCOL_H_ + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +// Endian: All wire-messages are in little-endian. + +#define PYRO_MAKE_MESSAGE_TYPE(t, s) ((((uint32_t)'P') << 26) | (((uint32_t)'Y') << 20) | (((uint32_t)'R') << 14) | (t) | ((s) << 6)) +#define PYRO_MESSAGE_MAGIC_MASK (~(uint32_t)0 << 14) +#define PYRO_MAX_PAYLOAD_SIZE 1024 + +typedef enum pyro_video_codec_type +{ + PYRO_VIDEO_CODEC_NONE = 0, + PYRO_VIDEO_CODEC_H264 = 1, + PYRO_VIDEO_CODEC_H265 = 2, + PYRO_VIDEO_CODEC_AV1 = 3, + PYRO_VIDEO_CODEC_MAX_INT = INT32_MAX +} pyro_video_codec_type; + +typedef enum pyro_audio_codec_type +{ + PYRO_AUDIO_CODEC_NONE = 0, + PYRO_AUDIO_CODEC_OPUS = 1, + PYRO_AUDIO_CODEC_AAC = 2, + PYRO_AUDIO_CODEC_RAW_S16LE = 3, + PYRO_AUDIO_CODEC_MAX_INT = INT32_MAX +} pyro_audio_codec_type; + +struct pyro_codec_parameters +{ + pyro_video_codec_type video_codec; + pyro_audio_codec_type audio_codec; + uint16_t frame_rate_num; + uint16_t frame_rate_den; + uint16_t width; + uint16_t height; + uint32_t channels; + uint32_t rate; +}; + +struct pyro_progress_report +{ + uint64_t total_received_packets; + uint64_t total_dropped_packets; +}; + +#define PYRO_MAX_UDP_DATAGRAM_SIZE (PYRO_MAX_PAYLOAD_SIZE + sizeof(struct pyro_codec_parameters)) + +// TCP: Server to client +// UDP / TCP: client to server +typedef enum pyro_message_type +{ + PYRO_MESSAGE_OK = PYRO_MAKE_MESSAGE_TYPE(0, 0), + PYRO_MESSAGE_NAK = PYRO_MAKE_MESSAGE_TYPE(1, 0), + PYRO_MESSAGE_AGAIN = PYRO_MAKE_MESSAGE_TYPE(2, 0), + // First message sent to server, server replies with COOKIE. + PYRO_MESSAGE_HELLO = PYRO_MAKE_MESSAGE_TYPE(3, 0), + // Returns a unique 64-bit cookie to client. + // Client must re-send that cookie over UDP. + PYRO_MESSAGE_COOKIE = PYRO_MAKE_MESSAGE_TYPE(4, sizeof(uint64_t)), + // Sent by client: Replies: CODEC_PARAMETERS if UDP cookie was received, NAK if not yet received or invalid. + // AGAIN is sent if UDP client is acknowledged, but stream is not ready yet (i.e. codec parameters are not known yet). + PYRO_MESSAGE_KICK = PYRO_MAKE_MESSAGE_TYPE(5, 0), + // Returns nothing. Must be received by server every 5 seconds or connection is dropped. + PYRO_MESSAGE_PROGRESS = PYRO_MAKE_MESSAGE_TYPE(6, sizeof(struct pyro_progress_report)), + PYRO_MESSAGE_CODEC_PARAMETERS = PYRO_MAKE_MESSAGE_TYPE(7, sizeof(struct pyro_codec_parameters)), + PYRO_MESSAGE_MAX_INT = INT32_MAX, +} pyro_message_type; + +#define PYRO_MAX_MESSAGE_BUFFER_LENGTH (255 + sizeof(pyro_message_type)) + +static inline bool pyro_message_validate_magic(uint32_t v) +{ + return PYRO_MAKE_MESSAGE_TYPE(0, 0) == (v & PYRO_MESSAGE_MAGIC_MASK); +} + +static inline uint32_t pyro_message_get_length(uint32_t v) +{ + return (v >> 6) & 0xff; +} + +// UDP: server to client. Size is implied by datagram. +enum pyro_payload_flag_bits +{ + PYRO_PAYLOAD_KEY_FRAME_BIT = 1 << 0, // For video, useful to know when clean recovery can be made, or when to start the stream + PYRO_PAYLOAD_STREAM_TYPE_BIT = 1 << 1, // 0: video, 1: audio + PYRO_PAYLOAD_PACKET_DONE_BIT = 1 << 2, // Set on last subpacket within a packet + PYRO_PAYLOAD_PACKET_BEGIN_BIT = 1 << 3, // Set on first subpacket within a packet + PYRO_PAYLOAD_PACKET_SEQ_OFFSET = 4, // Sequence increases by one on a per-stream basis. + PYRO_PAYLOAD_PACKET_SEQ_BITS = 14, + PYRO_PAYLOAD_SUBPACKET_SEQ_OFFSET = 18, + PYRO_PAYLOAD_SUBPACKET_SEQ_BITS = 14, +}; +typedef uint32_t pyro_payload_flags; + +#define PYRO_PAYLOAD_PACKET_SEQ_MASK ((1 << PYRO_PAYLOAD_PACKET_SEQ_BITS) - 1) +#define PYRO_PAYLOAD_SUBPACKET_SEQ_MASK ((1 << PYRO_PAYLOAD_SUBPACKET_SEQ_BITS) - 1) + +static inline uint32_t pyro_payload_get_packet_seq(pyro_payload_flags flags) +{ + return (flags >> PYRO_PAYLOAD_PACKET_SEQ_OFFSET) & PYRO_PAYLOAD_PACKET_SEQ_MASK; +} + +static inline uint32_t pyro_payload_get_subpacket_seq(pyro_payload_flags flags) +{ + return (flags >> PYRO_PAYLOAD_SUBPACKET_SEQ_OFFSET) & PYRO_PAYLOAD_SUBPACKET_SEQ_MASK; +} + +static inline int pyro_payload_get_seq_delta(uint32_t a, uint32_t b, uint32_t mask) +{ + uint32_t d = (a - b) & mask; + if (d <= (mask >> 1)) + return int(d); + else + return int(d) - int(mask + 1); +} + +static inline int pyro_payload_get_packet_seq_delta(uint32_t a, uint32_t b) +{ + return pyro_payload_get_seq_delta(a, b, PYRO_PAYLOAD_PACKET_SEQ_MASK); +} + +static inline int pyro_payload_get_subpacket_seq_delta(uint32_t a, uint32_t b) +{ + return pyro_payload_get_seq_delta(a, b, PYRO_PAYLOAD_SUBPACKET_SEQ_MASK); +} + +struct pyro_payload_header +{ + uint32_t pts_lo, pts_hi; + uint32_t dts_delta; // dts = pts - dts_delta + pyro_payload_flags encoded; +}; + +#ifdef __cplusplus +} +#endif + +#endif