diff --git a/MediaServer/CMakeLists.txt b/MediaServer/CMakeLists.txt index 2909f4a..f93fdbe 100644 --- a/MediaServer/CMakeLists.txt +++ b/MediaServer/CMakeLists.txt @@ -1,3 +1,107 @@ +add_library(libflv + libflv/source/amf0.c + libflv/source/avswg-avs3.c + libflv/source/flv-header.c + libflv/source/flv-reader.c + libflv/source/hevc-mp4toannexb.c + libflv/source/mpeg4-aac.c + libflv/source/mpeg4-hevc.c + libflv/source/opus-head.c + libflv/source/vvc-mp4toannexb.c + libflv/source/amf3.c + libflv/source/flv-demuxer-script.c + libflv/source/flv-muxer.c + libflv/source/flv-writer.c + libflv/source/mp3-header.c + libflv/source/mpeg4-annexbtomp4.c + libflv/source/mpeg4-mp4toannexb.c + libflv/source/riff-acm.c + libflv/source/webm-vpx.c + libflv/source/aom-av1.c + libflv/source/flv-demuxer.c + libflv/source/flv-parser.c + libflv/source/hevc-annexbtomp4.c + libflv/source/mpeg4-aac-asc.c + libflv/source/mpeg4-avc.c + libflv/source/mpeg4-vvc.c + libflv/source/vvc-annexbtomp4.c +) + +target_include_directories(libflv + PUBLIC libflv/include +) + +add_library(libmov + libmov/source/fmp4-reader.c + libmov/source/mov-dinf.c + libmov/source/mov-ftyp.c + libmov/source/mov-leva.c + libmov/source/mov-mfhd.c + libmov/source/mov-opus.c + libmov/source/mov-stco.c + libmov/source/mov-stss.c + libmov/source/mov-tag.c + libmov/source/mov-tfra.c + libmov/source/mov-trex.c + libmov/source/mov-udta.c + libmov/source/fmp4-writer.c + libmov/source/mov-elst.c + libmov/source/mov-hdlr.c + libmov/source/mov-iods.c + libmov/source/mov-mdhd.c + libmov/source/mov-minf.c + libmov/source/mov-reader.c + libmov/source/mov-stsc.c + libmov/source/mov-stsz.c + libmov/source/mov-tfdt.c + libmov/source/mov-tkhd.c + libmov/source/mov-trun.c + libmov/source/mov-vpcc.c + libmov/source/mov-avc1.c + libmov/source/mov-esds.c + libmov/source/mov-hdr.c + libmov/source/mov-mehd.c + libmov/source/mov-mvhd.c + libmov/source/mov-sidx.c + libmov/source/mov-stsd.c + libmov/source/mov-stts.c + libmov/source/mov-tfhd.c + libmov/source/mov-track.c + libmov/source/mov-tx3g.c + libmov/source/mov-writer.c +) + +target_include_directories(libmov + PUBLIC libmov/include +) + +add_library(libmpeg + libmpeg/source/mpeg-crc32.c + libmpeg/source/mpeg-muxer.c + libmpeg/source/mpeg-packet.c + libmpeg/source/mpeg-pmt.c + libmpeg/source/mpeg-ps-enc.c + libmpeg/source/mpeg-psd.c + libmpeg/source/mpeg-sdt.c + libmpeg/source/mpeg-ts-dec.c + libmpeg/source/mpeg-ts-h264.c + libmpeg/source/mpeg-ts-h266.c + libmpeg/source/mpeg-element-descriptor.c + libmpeg/source/mpeg-pack-header.c + libmpeg/source/mpeg-pat.c + libmpeg/source/mpeg-pes.c + libmpeg/source/mpeg-ps-dec.c + libmpeg/source/mpeg-psm.c + libmpeg/source/mpeg-system-header.c + libmpeg/source/mpeg-ts-enc.c + libmpeg/source/mpeg-ts-h265.c + libmpeg/source/mpeg-util.c +) + +target_include_directories(libmpeg + PUBLIC libmpeg/include +) + add_library(MediaServer Common/config.h Common/config.cpp Common/macros.h Common/macros.cpp @@ -128,6 +232,11 @@ add_library(MediaServer MediaServer.h MediaServer.cpp ) +target_compile_definitions(MediaServer + PUBLIC ENABLE_HLS + PUBLIC ENABLE_MP4 +) + target_include_directories(MediaServer PUBLIC ${CMAKE_CURRENT_SOURCE_DIR} PRIVATE /opt/Libraries/ZLMediaKit/include @@ -140,4 +249,7 @@ target_link_directories(MediaServer target_link_libraries(MediaServer PUBLIC ToolKit PUBLIC Universal + PRIVATE libflv + PRIVATE libmov + PRIVATE libmpeg ) \ No newline at end of file diff --git a/MediaServer/libflv/include/amf0.h b/MediaServer/libflv/include/amf0.h new file mode 100644 index 0000000..432a6c3 --- /dev/null +++ b/MediaServer/libflv/include/amf0.h @@ -0,0 +1,69 @@ +#ifndef _amf0_h_ +#define _amf0_h_ + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +enum AMFDataType +{ + AMF_NUMBER = 0x00, + AMF_BOOLEAN, + AMF_STRING, + AMF_OBJECT, + AMF_MOVIECLIP, + AMF_NULL, + AMF_UNDEFINED, + AMF_REFERENCE, + AMF_ECMA_ARRAY, + AMF_OBJECT_END, + AMF_STRICT_ARRAY, + AMF_DATE, + AMF_LONG_STRING, + AMF_UNSUPPORTED, + AMF_RECORDSET, + AMF_XML_DOCUMENT, + AMF_TYPED_OBJECT, + AMF_AVMPLUS_OBJECT, +}; + +uint8_t* AMFWriteNull(uint8_t* ptr, const uint8_t* end); +uint8_t* AMFWriteUndefined(uint8_t* ptr, const uint8_t* end); +uint8_t* AMFWriteObject(uint8_t* ptr, const uint8_t* end); +uint8_t* AMFWriteObjectEnd(uint8_t* ptr, const uint8_t* end); +uint8_t* AMFWriteTypedObject(uint8_t* ptr, const uint8_t* end); +uint8_t* AMFWriteECMAArarry(uint8_t* ptr, const uint8_t* end); + +uint8_t* AMFWriteBoolean(uint8_t* ptr, const uint8_t* end, uint8_t value); +uint8_t* AMFWriteDouble(uint8_t* ptr, const uint8_t* end, double value); +uint8_t* AMFWriteString(uint8_t* ptr, const uint8_t* end, const char* string, size_t length); +uint8_t* AMFWriteDate(uint8_t* ptr, const uint8_t* end, double milliseconds, int16_t timezone); + +uint8_t* AMFWriteNamedString(uint8_t* ptr, const uint8_t* end, const char* name, size_t length, const char* value, size_t length2); +uint8_t* AMFWriteNamedDouble(uint8_t* ptr, const uint8_t* end, const char* name, size_t length, double value); +uint8_t* AMFWriteNamedBoolean(uint8_t* ptr, const uint8_t* end, const char* name, size_t length, uint8_t value); + +const uint8_t* AMFReadNull(const uint8_t* ptr, const uint8_t* end); +const uint8_t* AMFReadUndefined(const uint8_t* ptr, const uint8_t* end); +const uint8_t* AMFReadBoolean(const uint8_t* ptr, const uint8_t* end, uint8_t* value); +const uint8_t* AMFReadDouble(const uint8_t* ptr, const uint8_t* end, double* value); +const uint8_t* AMFReadString(const uint8_t* ptr, const uint8_t* end, int isLongString, char* string, size_t length); +const uint8_t* AMFReadDate(const uint8_t* ptr, const uint8_t* end, double *milliseconds, int16_t *timezone); + + +struct amf_object_item_t +{ + enum AMFDataType type; + const char* name; + void* value; + size_t size; +}; +const uint8_t* amf_read_items(const uint8_t* data, const uint8_t* end, struct amf_object_item_t* items, size_t count); + +#ifdef __cplusplus +} +#endif +#endif /* !_amf0_h_ */ diff --git a/MediaServer/libflv/include/amf3.h b/MediaServer/libflv/include/amf3.h new file mode 100644 index 0000000..cf1fede --- /dev/null +++ b/MediaServer/libflv/include/amf3.h @@ -0,0 +1,56 @@ +#ifndef _amf3_h_ +#define _amf3_h_ + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +enum AMF3DataType +{ + AMF3_UNDEFINED = 0x00, + AMF3_NULL, + AMF3_FALSE, + AMF3_TRUE, + AMF3_INTEGER, + AMF3_DOUBLE, + AMF3_STRING, + AMF3_XML_DOCUMENT, + AMF3_DATE, + AMF3_ARRAY, + AMF3_OBJECT, + AMF3_XML, + AMF3_BYTE_ARRAY, + AMF3_VECTOR_INT, + AMF3_VECTOR_UINT, + AMF3_VECTOR_DOUBLE, + AMF3_VECTOR_OBJECT, + AMF3_DICTIONARY, +}; + +//uint8_t* AMF3WriteNull(uint8_t* ptr, const uint8_t* end); +//uint8_t* AMF3WriteObject(uint8_t* ptr, const uint8_t* end); +//uint8_t* AMF3WriteObjectEnd(uint8_t* ptr, const uint8_t* end); +// +//uint8_t* AMF3WriteBoolean(uint8_t* ptr, const uint8_t* end, uint8_t value); +//uint8_t* AMF3WriteInteger(uint8_t* ptr, const uint8_t* end, int32_t value); +//uint8_t* AMF3WriteDouble(uint8_t* ptr, const uint8_t* end, double value); +//uint8_t* AMF3WriteString(uint8_t* ptr, const uint8_t* end, const char* string, size_t length); +// +//uint8_t* AMF3WriteNamedBoolean(uint8_t* ptr, const uint8_t* end, const char* name, size_t length, uint8_t value); +//uint8_t* AMF3WriteNamedInteger(uint8_t* ptr, const uint8_t* end, const char* name, size_t length, int32_t value); +//uint8_t* AMF3WriteNamedString(uint8_t* ptr, const uint8_t* end, const char* name, size_t length, const char* value, size_t length2); +//uint8_t* AM3FWriteNamedDouble(uint8_t* ptr, const uint8_t* end, const char* name, size_t length, double value); + +const uint8_t* AMF3ReadNull(const uint8_t* ptr, const uint8_t* end); +const uint8_t* AMF3ReadBoolean(const uint8_t* ptr, const uint8_t* end); +const uint8_t* AMF3ReadInteger(const uint8_t* ptr, const uint8_t* end, int32_t* value); +const uint8_t* AMF3ReadDouble(const uint8_t* ptr, const uint8_t* end, double* value); +const uint8_t* AMF3ReadString(const uint8_t* ptr, const uint8_t* end, char* string, uint32_t* length); + +#ifdef __cplusplus +} +#endif +#endif /* !_amf3_h_ */ diff --git a/MediaServer/libflv/include/aom-av1.h b/MediaServer/libflv/include/aom-av1.h new file mode 100644 index 0000000..e610b69 --- /dev/null +++ b/MediaServer/libflv/include/aom-av1.h @@ -0,0 +1,54 @@ +#ifndef _aom_av1_h_ +#define _aom_av1_h_ + +#include +#include + +#if defined(__cplusplus) +extern "C" { +#endif + +struct aom_av1_t +{ + uint32_t marker : 1; + uint32_t version : 7; + uint32_t seq_profile : 3; + uint32_t seq_level_idx_0 : 5; + uint32_t seq_tier_0 : 1; + uint32_t high_bitdepth : 1; + uint32_t twelve_bit : 1; + uint32_t monochrome : 1; + uint32_t chroma_subsampling_x : 1; + uint32_t chroma_subsampling_y : 1; + uint32_t chroma_sample_position : 2; + + uint32_t reserved : 3; + uint32_t initial_presentation_delay_present : 1; + uint32_t initial_presentation_delay_minus_one : 4; + + uint8_t buffer_delay_length_minus_1; // decoder_model_info + uint32_t width; // max_frame_width_minus_1 + uint32_t height; // max_frame_height_minus_1 + + uint16_t bytes; + uint8_t data[2 * 1024]; +}; + +/// Create av1 codec configuration record from Sequence Header OBU +/// @param[in] data av1 low overhead bitstream format +/// @return 0-ok, other-error +int aom_av1_codec_configuration_record_init(struct aom_av1_t* av1, const void* data, size_t bytes); + +int aom_av1_codec_configuration_record_load(const uint8_t* data, size_t bytes, struct aom_av1_t* av1); +int aom_av1_codec_configuration_record_save(const struct aom_av1_t* av1, uint8_t* data, size_t bytes); + +/// @param[in] data av1 split low overhead/annexb bitstream format to obu +int aom_av1_obu_split(const uint8_t* data, size_t bytes, int (*handler)(void* param, const uint8_t* obu, size_t bytes), void* param); +int aom_av1_annexb_split(const uint8_t* data, size_t bytes, int (*handler)(void* param, const uint8_t* obu, size_t bytes), void* param); + +int aom_av1_codecs(const struct aom_av1_t* av1, char* codecs, size_t bytes); + +#if defined(__cplusplus) +} +#endif +#endif /* !_aom_av1_h_ */ diff --git a/MediaServer/libflv/include/avswg-avs3.h b/MediaServer/libflv/include/avswg-avs3.h new file mode 100644 index 0000000..e0969b5 --- /dev/null +++ b/MediaServer/libflv/include/avswg-avs3.h @@ -0,0 +1,37 @@ +#ifndef _avswg_avs3_h_ +#define _avswg_avs3_h_ + +// http://standard.avswg.org.cn/avs3_download/ + +#include +#include + +#if defined(__cplusplus) +extern "C" { +#endif + +struct avswg_avs3_t +{ + uint32_t version : 8; + uint32_t sequence_header_length : 16; + uint32_t library_dependency_idc : 2; + + uint8_t sequence_header[2 * 1024]; // bytes: sequence_header_length +}; +/// Create avs3 codec configuration record from bitstream +/// @param[in] data avs3 bitstream format(00 00 01 B0) +/// @return 0-ok, other-error +int avswg_avs3_decoder_configuration_record_init(struct avswg_avs3_t* avs3, const void* data, size_t bytes); + +// load avs3 from Avs3DecoderConfigurationRecord +int avswg_avs3_decoder_configuration_record_load(const uint8_t* data, size_t bytes, struct avswg_avs3_t* avs3); + +int avswg_avs3_decoder_configuration_record_save(const struct avswg_avs3_t* avs3, uint8_t* data, size_t bytes); + +int avswg_avs3_codecs(const struct avswg_avs3_t* avs3, char* codecs, size_t bytes); + +#if defined(__cplusplus) +} +#endif + +#endif /* !_avswg_avs3_h_ */ diff --git a/MediaServer/libflv/include/flv-demuxer.h b/MediaServer/libflv/include/flv-demuxer.h new file mode 100644 index 0000000..b093047 --- /dev/null +++ b/MediaServer/libflv/include/flv-demuxer.h @@ -0,0 +1,38 @@ +#ifndef _flv_demuxer_h_ +#define _flv_demuxer_h_ + +#include +#include + +#if defined(__cplusplus) +extern "C" { +#endif + +typedef struct flv_demuxer_t flv_demuxer_t; + +/// Audio/Video Elementary Stream +/// @param[in] param user-defined parameter +/// @param[in] codec audio/video format (see more flv-proto.h) +/// @param[in] data audio/video element data, AAC: ADTS + AAC-Frame, H.264: startcode + NALU, MP3-Raw data +/// @param[in] bytes data length in byte +/// @param[in] pts audio/video presentation timestamp +/// @param[in] dts audio/video decoding timestamp +/// @param[in] flags 1-video keyframe, other-undefined +/// @return 0-ok, other-error +typedef int (*flv_demuxer_handler)(void* param, int codec, const void* data, size_t bytes, uint32_t pts, uint32_t dts, int flags); + +flv_demuxer_t* flv_demuxer_create(flv_demuxer_handler handler, void* param); +void flv_demuxer_destroy(flv_demuxer_t* demuxer); + +/// Input FLV Audio/Video Stream +/// @param[in] type 8-audio, 9-video, 18-script (see more flv-proto.h) +/// @param[in] data flv audio/video Stream, AudioTagHeader/VideoTagHeader + A/V Data +/// @param[in] bytes data length in byte +/// @param[in] timestamp milliseconds relative to the first tag(DTS) +/// @return 0-ok, other-error +int flv_demuxer_input(flv_demuxer_t* demuxer, int type, const void* data, size_t bytes, uint32_t timestamp); + +#if defined(__cplusplus) +} +#endif +#endif /* !_flv_demuxer_h_ */ diff --git a/MediaServer/libflv/include/flv-header.h b/MediaServer/libflv/include/flv-header.h new file mode 100644 index 0000000..e11f1d4 --- /dev/null +++ b/MediaServer/libflv/include/flv-header.h @@ -0,0 +1,118 @@ +#ifndef _flv_header_h_ +#define _flv_header_h_ + +#include +#include + +#if defined(__cplusplus) +extern "C" { +#endif + +struct flv_header_t +{ + uint8_t FLV[3]; + uint8_t version; + uint8_t audio; + uint8_t video; + uint32_t offset; // data offset +}; + +struct flv_tag_header_t +{ + uint8_t filter; // 0-No pre-processing required + uint8_t type; // 8-audio, 9-video, 18-script data + uint32_t size; // data size + uint32_t timestamp; + uint32_t streamId; +}; + +struct flv_audio_tag_header_t +{ + uint8_t codecid; /// audio codec id: FLV_AUDIO_AAC + uint8_t rate; /// audio sample frequence: 0-5.5 kHz, 1-11 kHz, 2-22 kHz, 3-44 kHz + uint8_t bits; /// audio sample bits: 0-8 bit samples, 1-16-bit samples + uint8_t channels; /// audio channel count: 0-Mono sound, 1-Stereo sound + uint8_t avpacket; /// AAC only:FLV_SEQUENCE_HEADER/FLV_AVPACKET +}; + +struct flv_video_tag_header_t +{ + uint8_t codecid; /// video codec id: FLV_VIDEO_H264 + uint8_t keyframe; /// video frame type: 1-key frame, 2-inter frame + uint8_t avpacket; /// H.264/H.265/AV1 only:FLV_SEQUENCE_HEADER/FLV_AVPACKET/FLV_END_OF_SEQUENCE + int32_t cts; /// video composition time(PTS - DTS), AVC/HEVC/AV1 only +}; + +/// Read FLV File Header +/// @return >=0-header length in byte, <0-error +int flv_header_read(struct flv_header_t* flv, const uint8_t* buf, size_t len); + +/// Write FLV File Header +/// @param[in] audio 1-has audio, 0-don't have +/// @param[in] video 1-has video, 0-don't have +/// @param[out] buf flv header buffer +/// @param[out] len flv header length +/// @return >=0-header length in byte, <0-error +int flv_header_write(int audio, int video, uint8_t* buf, size_t len); + + +/// Read FLV Tag Header +/// @return >=0-header length in byte, <0-error +int flv_tag_header_read(struct flv_tag_header_t* tag, const uint8_t* buf, size_t len); + +/// Write FLV Tag Header +/// @param[out] buf flv tag header buffer +/// @param[out] len flv tag header length +/// @return >=0-header length in byte, <0-error +int flv_tag_header_write(const struct flv_tag_header_t* tag, uint8_t* buf, size_t len); + + +/// Read FLV Audio Tag Header +/// @param[out] audio flv audio parameter +/// @param[in] buf flv audio tag header buffer +/// @param[in] len flv audio tag header length +/// @return >=0-header length in byte, <0-error +int flv_audio_tag_header_read(struct flv_audio_tag_header_t* audio, const uint8_t* buf, size_t len); + +/// Write FLV Audio Tag Header +/// @param[in] audio flv audio parameter +/// @param[out] buf flv audio tag header buffer +/// @param[out] len flv audio tag header length +/// @return >=0-header length in byte, <0-error +int flv_audio_tag_header_write(const struct flv_audio_tag_header_t* audio, uint8_t* buf, size_t len); + + +/// Read FLV Video Tag Header +/// @param[out] video flv video parameter +/// @param[in] buf flv video tag header buffer +/// @param[in] len flv video tag header length +/// @return >=0-header length in byte, <0-error +int flv_video_tag_header_read(struct flv_video_tag_header_t* video, const uint8_t* buf, size_t len); + +/// Write FLV Video Tag Header +/// @param[in] video flv video parameter +/// @param[out] buf flv video tag header buffer +/// @param[out] len flv video tag header length +/// @return >=0-header length in byte, <0-error +int flv_video_tag_header_write(const struct flv_video_tag_header_t* video, uint8_t* buf, size_t len); + + +/// Read FLV Data Tag Header +/// @return >=0-header length in byte, <0-error +int flv_data_tag_header_read(const uint8_t* buf, size_t len); + +/// Write FLV Data Tag Header +/// @param[out] buf flv data tag header buffer +/// @param[out] len flv data tag header length +/// @return >=0-header length in byte, <0-error +int flv_data_tag_header_write(uint8_t* buf, size_t len); + + +/// Read/Write FLV previous tag size +int flv_tag_size_read(const uint8_t* buf, size_t len, uint32_t* size); +int flv_tag_size_write(uint8_t* buf, size_t len, uint32_t size); + +#if defined(__cplusplus) +} +#endif +#endif /* !_flv_header_h_ */ diff --git a/MediaServer/libflv/include/flv-muxer.h b/MediaServer/libflv/include/flv-muxer.h new file mode 100644 index 0000000..1afe22b --- /dev/null +++ b/MediaServer/libflv/include/flv-muxer.h @@ -0,0 +1,73 @@ +#ifndef _flv_muxer_h_ +#define _flv_muxer_h_ + +#include +#include + +#if defined(__cplusplus) +extern "C" { +#endif + +typedef struct flv_muxer_t flv_muxer_t; + +///Video: FLV VideoTagHeader + AVCVIDEOPACKET: AVCDecoderConfigurationRecord(ISO 14496-15) / One or more NALUs(four-bytes length + NALU) +///Audio: FLV AudioTagHeader + AACAUDIODATA: AudioSpecificConfig(14496-3) / Raw AAC frame data in UI8 +///@param[in] data FLV Audio/Video Data(don't include FLV Tag Header) +///@param[in] type 8-audio, 9-video +///@return 0-ok, other-error +typedef int (*flv_muxer_handler)(void* param, int type, const void* data, size_t bytes, uint32_t timestamp); + +flv_muxer_t* flv_muxer_create(flv_muxer_handler handler, void* param); +void flv_muxer_destroy(flv_muxer_t* muxer); + +/// re-create AAC/AVC sequence header +int flv_muxer_reset(flv_muxer_t* muxer); + +/// @param[in] data AAC ADTS stream, 0xFFF15C40011FFC... +int flv_muxer_aac(flv_muxer_t* muxer, const void* data, size_t bytes, uint32_t pts, uint32_t dts); + +/// @param[in] data mp3 stream +int flv_muxer_mp3(flv_muxer_t* muxer, const void* data, size_t bytes, uint32_t pts, uint32_t dts); + +/// g711 alaw/mu-law +int flv_muxer_g711a(flv_muxer_t* muxer, const void* data, size_t bytes, uint32_t pts, uint32_t dts); +int flv_muxer_g711u(flv_muxer_t* muxer, const void* data, size_t bytes, uint32_t pts, uint32_t dts); + +/// @param[in] data opus stream, first opus head, then opus samples +int flv_muxer_opus(flv_muxer_t* muxer, const void* data, size_t bytes, uint32_t pts, uint32_t dts); + +/// @param[in] data h.264 annexb bitstream: H.264 start code + H.264 NALU, 0x0000000168... +int flv_muxer_avc(flv_muxer_t* muxer, const void* data, size_t bytes, uint32_t pts, uint32_t dts); + +/// @param[in] data h.265 annexb bitstream: H.265 start code + H.265 NALU, 0x00000001... +int flv_muxer_hevc(flv_muxer_t* muxer, const void* data, size_t bytes, uint32_t pts, uint32_t dts); + +/// @param[in] data av1 low overhead bitstream format +int flv_muxer_av1(flv_muxer_t* muxer, const void* data, size_t bytes, uint32_t pts, uint32_t dts); + +/// @param[in] data avs3 bitstream (00 00 01 B0 ...) +int flv_muxer_avs3(flv_muxer_t* muxer, const void* data, size_t bytes, uint32_t pts, uint32_t dts); + +struct flv_metadata_t +{ + int audiocodecid; + double audiodatarate; // kbps + int audiosamplerate; + int audiosamplesize; + int stereo; + + int videocodecid; + double videodatarate; // kbps + double framerate; // fps + double duration; + int interval; // frame interval + int width; + int height; +}; + +int flv_muxer_metadata(flv_muxer_t* muxer, const struct flv_metadata_t* metadata); + +#if defined(__cplusplus) +} +#endif +#endif /* !_flv_muxer_h_ */ diff --git a/MediaServer/libflv/include/flv-parser.h b/MediaServer/libflv/include/flv-parser.h new file mode 100644 index 0000000..413661c --- /dev/null +++ b/MediaServer/libflv/include/flv-parser.h @@ -0,0 +1,53 @@ +#ifndef _flv_parser_h_ +#define _flv_parser_h_ + +#include +#include +#include "flv-header.h" + +#if defined(__cplusplus) +extern "C" { +#endif + +/// Audio/Video Elementary Stream +/// @param[in] param user-defined parameter +/// @param[in] codec audio/video format (see more flv-proto.h) +/// @param[in] data audio/video element data, AAC: AAC-Frame, H.264: MP4 Stream, MP3-Raw data +/// @param[in] bytes data length in byte +/// @param[in] pts audio/video presentation timestamp +/// @param[in] dts audio/video decoding timestamp +/// @param[in] flags 1-video keyframe, other-undefined +/// @return 0-ok, other-error +typedef int (*flv_parser_handler)(void* param, int codec, const void* data, size_t bytes, uint32_t pts, uint32_t dts, int flags); + +/// Input FLV Audio/Video Stream +/// @param[in] type 8-audio, 9-video, 18-script (see more flv-proto.h) +/// @param[in] data flv audio/video Stream, AudioTagHeader/VideoTagHeader + A/V Data +/// @param[in] bytes data length in byte +/// @param[in] timestamp milliseconds relative to the first tag(DTS) +/// @return 0-ok, other-error +int flv_parser_tag(int type, const void* data, size_t bytes, uint32_t timestamp, flv_parser_handler handler, void* param); + +struct flv_parser_t +{ + int state; + + size_t bytes; + size_t expect; + uint8_t ptr[32]; + struct flv_header_t header; + struct flv_tag_header_t tag; + struct flv_audio_tag_header_t audio; + struct flv_video_tag_header_t video; + + uint8_t* body; + void* (*alloc)(void* param, size_t bytes); + void (*free)(void* param, void* ptr); +}; + +int flv_parser_input(struct flv_parser_t* parser, const uint8_t* data, size_t bytes, flv_parser_handler handler, void* param); + +#if defined(__cplusplus) +} +#endif +#endif /* !_flv_parser_h_ */ diff --git a/MediaServer/libflv/include/flv-proto.h b/MediaServer/libflv/include/flv-proto.h new file mode 100644 index 0000000..1eb4382 --- /dev/null +++ b/MediaServer/libflv/include/flv-proto.h @@ -0,0 +1,105 @@ +#ifndef _flv_proto_h_ +#define _flv_proto_h_ + +// FLV Tag Type +#define FLV_TYPE_AUDIO 8 +#define FLV_TYPE_VIDEO 9 +#define FLV_TYPE_SCRIPT 18 + +// FLV Audio Type +#define FLV_AUDIO_LPCM (0 << 4) // Linear PCM, platform endian +#define FLV_AUDIO_ADPCM (1 << 4) +#define FLV_AUDIO_MP3 (2 << 4) +#define FLV_AUDIO_LLPCM (3 << 4) // Linear PCM, little endian +#define FLV_AUDIO_G711A (7 << 4) // G711 A-law +#define FLV_AUDIO_G711U (8 << 4) // G711 mu-law +#define FLV_AUDIO_AAC (10 << 4) +#define FLV_AUDIO_SPEEX (11 << 4) +#define FLV_AUDIO_OPUS (13 << 4) +#define FLV_AUDIO_MP3_8K (14 << 4) // MP3 8 kHz +#define FLV_AUDIO_DEVIDE (15 << 4) // Device-specific sound +#define FLV_AUDIO_ASC (0x1000 | FLV_AUDIO_AAC) // AudioSpecificConfig(ISO-14496-3) +#define FLV_AUDIO_OPUS_HEAD (0x1100 | FLV_AUDIO_OPUS)// opus-codec.org + +// FLV Video Type +#define FLV_VIDEO_H263 2 // Sorenson H.263 +#define FLV_VIDEO_SCREEN 3 // Screen video +#define FLV_VIDEO_VP6 4 // On2 VP6 +#define FLV_VIDEO_H264 7 // AVC +#define FLV_VIDEO_H265 12 // https://github.com/CDN-Union/H265 +#define FLV_VIDEO_AV1 13 // https://aomediacodec.github.io/av1-isobmff +#define FLV_VIDEO_AVS3 14 +#define FLV_VIDEO_H266 15 +#define FLV_VIDEO_AVCC (0x2000 | FLV_VIDEO_H264) // AVCDecoderConfigurationRecord(ISO-14496-15) +#define FLV_VIDEO_HVCC (0x2100 | FLV_VIDEO_H265) // HEVCDecoderConfigurationRecord(ISO-14496-15) +#define FLV_VIDEO_AV1C (0x2200 | FLV_VIDEO_AV1) // AV1CodecConfigurationRecord(av1-isobmff) +#define FLV_VIDEO_AVSC (0x2300 | FLV_VIDEO_AVS3) // AVS3DecoderConfigurationRecord +#define FLV_VIDEO_VVCC (0x2400 | FLV_VIDEO_H266) // VVCDecoderConfigurationRecord(ISO-14496-15) + +#define FLV_SCRIPT_METADATA 0x4000 // onMetaData + +enum +{ + FLV_SEQUENCE_HEADER = 0, // AVC/AAC sequence header + FLV_AVPACKET = 1, // AVC NALU / AAC raw + FLV_END_OF_SEQUENCE = 2, // AVC end of sequence (lower level NALU sequence ender is not required or supported) + + // CompositionTime Offset is implied to equal zero. This is + // an optimization to save putting SI24 composition time value of zero on + // the wire. See pseudo code below in the VideoTagBody section + FLV_PACKET_TYPE_CODED_FRAMES_X = 3, + + // VideoTagBody does not contain video data. VideoTagBody + // instead contains an AMF encoded metadata. See Metadata Frame + // section for an illustration of its usage. As an example, the metadata + // can be HDR information. This is a good way to signal HDR + // information. This also opens up future ways to express additional + // metadata that is meant for the next video sequence. + // + // note: presence of PacketTypeMetadata means that FrameType + // flags at the top of this table should be ignored + FLV_PACKET_TYPE_METADATA = 4, + + // Carriage of bitstream in MPEG-2 TS format + // note: PacketTypeSequenceStart and PacketTypeMPEG2TSSequenceStart + // are mutually exclusive + FLV_PACKET_TYPE_MPEG2TS_SEQUENCE_START = 5, +}; + +enum +{ + FLV_VIDEO_KEY_FRAME = 1, // key frame (for AVC, a seekable frame) + FLV_VIDEO_INTER_FRAME = 2, // inter frame (for AVC, a non-seekable frame) + FLV_VIDEO_DISPOSABLE_INTER_FRAME = 3, // H.263 only + FLV_VIDEO_GENERATED_KEY_FRAME = 4, // generated key frame (reserved for server use only) + FLV_VIDEO_COMMAND_FRAME = 5, // video info/command frame +}; + +enum +{ + FLV_SOUND_RATE_5500 = 0, // 5.5 kHz + FLV_SOUND_RATE_11025 = 1, // 11 kHz + FLV_SOUND_RATE_22050 = 2, // 22 kHz + FLV_SOUND_RATE_44100 = 3, // 44 kHz +}; + +enum +{ + FLV_SOUND_BIT_8 = 0, // 8-bit samples + FLV_SOUND_BIT_16 = 1, // 16-bit samples +}; + +enum +{ + FLV_SOUND_CHANNEL_MONO = 0, // 1-channel + FLV_SOUND_CHANNEL_STEREO = 1, // 2-channels +}; + +#define FLV_VIDEO_FOURCC(a, b, c, d) (((a) << 24) | ((b) << 16) | ((c) << 8) | (d)) + +#define FLV_VIDEO_FOURCC_AV1 FLV_VIDEO_FOURCC('a', 'v', '0', '1') +#define FLV_VIDEO_FOURCC_VP9 FLV_VIDEO_FOURCC('v', 'p', '0', '9') +#define FLV_VIDEO_FOURCC_HEVC FLV_VIDEO_FOURCC('h', 'v', 'c', '1') +#define FLV_VIDEO_FOURCC_VVC FLV_VIDEO_FOURCC('v', 'v', 'c', '1') + +#endif /* !_flv_proto_h_ */ diff --git a/MediaServer/libflv/include/flv-reader.h b/MediaServer/libflv/include/flv-reader.h new file mode 100644 index 0000000..69f9c61 --- /dev/null +++ b/MediaServer/libflv/include/flv-reader.h @@ -0,0 +1,26 @@ +#ifndef _flv_reader_h_ +#define _flv_reader_h_ + +#include +#include + +#if defined(__cplusplus) +extern "C" { +#endif + +void* flv_reader_create(const char* file); +void* flv_reader_create2(int(*read)(void* param, void* buf, int len), void* param); +void flv_reader_destroy(void* flv); + +///@param[out] tagtype 8-audio, 9-video, 18-script data +///@param[out] timestamp FLV timestamp +///@param[out] taglen flv tag length(0 is ok but should be silently discard) +///@param[out] buffer FLV stream +///@param[in] bytes buffer size +///@return 1-got a packet, 0-EOF, other-error +int flv_reader_read(void* flv, int* tagtype, uint32_t* timestamp, size_t* taglen, void* buffer, size_t bytes); + +#if defined(__cplusplus) +} +#endif +#endif /* !_flv_reader_h_ */ diff --git a/MediaServer/libflv/include/flv-writer.h b/MediaServer/libflv/include/flv-writer.h new file mode 100644 index 0000000..90f54a8 --- /dev/null +++ b/MediaServer/libflv/include/flv-writer.h @@ -0,0 +1,41 @@ +#ifndef _flv_writer_h_ +#define _flv_writer_h_ + +#include +#include + +#if defined(__cplusplus) +extern "C" { +#endif + +struct flv_vec_t +{ + void* ptr; + int len; +}; + +/// @param[in] param flv_writer_create2 param +/// @param[in] n vec number +/// @return 0-ok, other-error +typedef int (*flv_writer_onwrite)(void* param, const struct flv_vec_t* vec, int n); + +void* flv_writer_create(const char* file); +/// @param[in] audio 1-has audio, 0-don't has audio +/// @param[in] video 1-has video, 0-don't has video +void* flv_writer_create2(int audio, int video, flv_writer_onwrite onwrite, void* param); + +void flv_writer_destroy(void* flv); + +/// Video: FLV VideoTagHeader + AVCVIDEOPACKET: AVCDecoderConfigurationRecord(ISO 14496-15) / One or more NALUs(four-bytes length + NALU) +/// Audio: FLV AudioTagHeader + AACAUDIODATA: AudioSpecificConfig(14496-3) / Raw AAC frame data in UI8 +/// @param[in] data FLV Audio/Video Data(don't include FLV Tag Header) +/// @param[in] type 8-audio, 9-video +/// @return 0-ok, other-error +int flv_writer_input(void* flv, int type, const void* data, size_t bytes, uint32_t timestamp); + +int flv_writer_input_v(void* flv, int type, const struct flv_vec_t* vec, int num, uint32_t timestamp); + +#if defined(__cplusplus) +} +#endif +#endif /* !_flv_writer_h_ */ diff --git a/MediaServer/libflv/include/mp3-header.h b/MediaServer/libflv/include/mp3-header.h new file mode 100644 index 0000000..0c3bb90 --- /dev/null +++ b/MediaServer/libflv/include/mp3-header.h @@ -0,0 +1,109 @@ +#ifndef _mp3_header_h_ +#define _mp3_header_h_ + +// https://en.wikipedia.org/wiki/MP3 + +#if defined(__cplusplus) +extern "C" { +#endif + +/* +ISO/IEC 11172-3 +2.4.1.3 Header +unsigned int sync: 12 +unsigned int version: 1 +unsigned int layer: 2 +unsigned int error protection: 1 +unsigned int bitrate_index: 4 +unsigned int sampling_frequency: 2 +unsigned int padding: 1 +unsigned int private: 1 +unsigned int mode: 2 +unsigned int mode extension: 2 +unsigned int copyright: 1 +unsigned int original: 1 +unsigned int emphasis: 2 + +bit_rate_index Layer I Layer II Layer III + '0000' free format free format free format + '0001' 32 kbit/s 32 kbit/s 32 kbit/s + '0010' 64 kbit/s 48 kbit/s 40 kbit/s + '0011' 96 kbit/s 56 kbit/s 48 kbit/s + '0100' 128 kbit/s 64 kbit/s 56 kbit/s + '0101' 160 kbit/s 80 kbit/s 64 kbit/s + '0110' 192 kbit/s 96 kbit/s 80 kbit/s + '0111' 224 kbit/s 112 kbit/s 96 kbit/s + '1000' 256 kbit/s 128 kbit/s 112 kbit/s + '1001' 288 kbit/s 160 kbit/s 128 kbit/s + '1010' 320 kbit/s 192 kbit/s 160 kbit/s + '1011' 352 kbit/s 224 kbit/s 192 kbit/s + '1100' 384 kbit/s 256 kbit/s 224 kbit/s + '1101' 416 kbit/s 320 kbit/s 256 kbit/s + '1110' 448 kbit/s 384 kbit/s 320 kbit/s + +sampling_frequency +'00' 44.1 kHz +'01' 48 kHz +'10' 32 kHz +'11' reserved + +mode +'00' stereo +'01' joint_stereo (intensity_stereo and/or ms_stereo) +'10' dual_channel +'11' single_channel + +mode_extension +'00' subbands 4-31 in intensity_stereo, bound==4 +'01' subbands 8-31 in intensity_stereo, bound==8 +'10' subbands 12-31 in intensity_stereo, bound==12 +'11' subbands 16-31 in intensity_stereo, bound==16 + +emphasis +'00' no emphasis +'01' 50/15 microsec. emphasis +'10' reserved +'11' CCITT J.17 +*/ + +struct mp3_header_t +{ + unsigned int version : 2; // 0-MPEG 2.5, 1-undefined, 2-MPEG-2, 3-MPEG-1 + unsigned int layer : 2; // 3-Layer I, 2-Layer II, 1-Layer III, 0-reserved + unsigned int protection : 1; + unsigned int bitrate_index : 4; //0-free, + unsigned int sampling_frequency : 2; + unsigned int priviate : 1; + unsigned int mode : 2; + unsigned int mode_extension : 2; + unsigned int copyright : 1; + unsigned int original : 1; + unsigned int emphasis : 2; +}; + +// version +#define MP3_MPEG1 3 +#define MP3_MPEG2 2 +#define MP3_MPEG2_5 0 + +// layer +#define MP3_LAYER1 3 +#define MP3_LAYER2 2 +#define MP3_LAYER3 1 + +#define MP3_BITS_PER_SAMPLE 16 + +///MP3 Header size: 4 +int mp3_header_load(struct mp3_header_t* mp3, const void* data, int bytes); +int mp3_header_save(const struct mp3_header_t* mp3, void* data, int bytes); + +int mp3_get_channel(const struct mp3_header_t* mp3); +int mp3_get_bitrate(const struct mp3_header_t* mp3); +int mp3_set_bitrate(struct mp3_header_t* mp3, int bitrate); +int mp3_get_frequency(const struct mp3_header_t* mp3); +int mp3_set_frequency(struct mp3_header_t* mp3, int frequency); + +#if defined(__cplusplus) +} +#endif +#endif /* !_mp3_header_h_ */ diff --git a/MediaServer/libflv/include/mpeg4-aac.h b/MediaServer/libflv/include/mpeg4-aac.h new file mode 100644 index 0000000..369a5b7 --- /dev/null +++ b/MediaServer/libflv/include/mpeg4-aac.h @@ -0,0 +1,156 @@ +#ifndef _mpeg4_aac_h_ +#define _mpeg4_aac_h_ + +#include +#include + +#if defined(__cplusplus) +extern "C" { +#endif + +struct mpeg4_aac_t +{ + uint8_t profile; // 0-NULL, 1-AAC Main, 2-AAC LC, 2-AAC SSR, 3-AAC LTP + uint8_t sampling_frequency_index; // 0-96000, 1-88200, 2-64000, 3-48000, 4-44100, 5-32000, 6-24000, 7-22050, 8-16000, 9-12000, 10-11025, 11-8000, 12-7350, 13/14-reserved, 15-frequency is written explictly + uint8_t channel_configuration; // 0-AOT, 1-1channel,front-center, 2-2channels, front-left/right, 3-3channels: front center/left/right, 4-4channels: front-center/left/right, back-center, 5-5channels: front center/left/right, back-left/right, 6-6channels: front center/left/right, back left/right LFE-channel, 7-8channels + + uint32_t sampling_frequency; // codec frequency, valid only in decode + uint32_t extension_frequency; // play frequency(AAC-HE v1/v2 sbr/ps) + uint8_t extension_audio_object_type; // default 0, valid on sbr/ps flag + uint8_t extension_channel_configuration; // default: channel_configuration + uint8_t channels; // valid only in decode + int sbr; // sbr flag, valid only in decode + int ps; // ps flag, valid only in decode + uint8_t pce[64]; + int npce; // pce bytes +}; + +enum mpeg2_aac_profile +{ + MPEG2_AAC_MAIN = 0, + MPEG2_AAC_LC, + MPEG2_AAC_SSR, +}; + +// ISO/IEC 14496-3:2009(E) Table 1.3 - Audio Profiles definition (p41) +// https://en.wikipedia.org/wiki/MPEG-4_Part_3#Audio_Profiles +enum mpeg4_aac_object_type +{ + MPEG4_AAC_MAIN = 1, + MPEG4_AAC_LC, + MPEG4_AAC_SSR, + MPEG4_AAC_LTP, + MPEG4_AAC_SBR, // (used with AAC LC in the "High Efficiency AAC Profile" (HE-AAC v1)) + MPEG4_AAC_SCALABLE, + MPEG4_AAC_TWINVQ, + MPEG4_AAC_CELP, + MPEG4_AAC_HVXC, + MPEG4_AAC_TTSI = 12, + MPEG4_AAC_MAIN_SYNTHETIC, + MPEG4_AAC_WAVETABLE_SYNTHETIC, + MPEG4_AAC_GENERAL_MIDI, + MPEG4_AAC_ALGORITHMIC_SYNTHESIS, // Algorithmic Synthesis and Audio FX object type + MPEG4_AAC_ER_LC, // Error Resilient (ER) AAC Low Complexity (LC) object type + MPEG4_AAC_ER_LTP = 19, // Error Resilient (ER) AAC Long Term Predictor (LTP) object type + MPEG4_AAC_ER_SCALABLE, // Error Resilient (ER) AAC scalable object type + MPEG4_AAC_ER_TWINVQ, // Error Resilient (ER) TwinVQ object type + MPEG4_AAC_ER_BSAC, // Error Resilient (ER) BSAC object type + MPEG4_AAC_ER_AAC_LD, // Error Resilient (ER) AAC LD object type(used with CELP, ER CELP, HVXC, ER HVXC and TTSI in the "Low Delay Profile") + MPEG4_AAC_ER_CELP, // Error Resilient (ER) CELP object type + MPEG4_AAC_ER_HVXC, // Error Resilient (ER) HVXC object type + MPEG4_AAC_ER_HILN, // Error Resilient (ER) HILN object type + MPEG4_AAC_ER_PARAMTRIC, // Error Resilient (ER) Parametric object type + MPEG4_AAC_SSC, // SSC Audio object type + MPEG4_AAC_PS, // PS object type(used with AAC LC and SBR in the "HE-AAC v2 Profile") + MPEG4_AAC_MPEG_SURROUND, // MPEG Surround object type + MPEG4_AAC_LAYER_1 = 32, // Layer-1 Audio object type + MPEG4_AAC_LAYER_2, // Layer-2 Audio object type + MPEG4_AAC_LAYER_3, // Layer-3 Audio object type + MPEG4_AAC_DST, + MPEG4_AAC_ALS, // ALS Audio object type + MPEG4_AAC_SLS, // SLS Audio object type + MPEG4_AAC_SLS_NON_CORE, // SLS Non-Core Audio object type + MPEG4_AAC_ER_AAC_ELD, // Error Resilient (ER) AAC ELD object type (uses AAC-LD, AAC-ELD and AAC-ELDv2, "Low Delay AAC v2") + MPEG4_AAC_SMR_SIMPLE, // SMR Simple object type: MPEG-4 Part 23 standard (ISO/IEC 14496-23:2008) + MPEG4_AAC_SMR_MAIN, // SMR Main object type + MPEG4_AAC_USAC_NO_SBR, // Unified Speech and Audio Coding (no SBR) + MPEG4_AAC_SAOC, // Spatial Audio Object Coding: MPEG-D Part 2 standard (ISO/IEC 23003-2:2010) + MPEG4_AAC_LD_MEPG_SURROUND, // MPEG-D Part 2 - ISO/IEC 23003-2 + MPEG4_AAC_USAC, // MPEG-D Part 3 - ISO/IEC 23003-3 +}; + +enum mpeg4_audio_profile +{ + MPEG4_AAC_PROFILE, // AAC LC + MPEG4_HIGH_EFFICIENCY_AAC_PROFILE, // AAC LC, SBR (<=128 kbps) + MPEG4_HE_AAC_V2_PROFILE, // AAC LC, SBR, PS (approx. 16 - 48 kbit/s) + MPEG4_MAIN_AUDIO_PROFILE, // AAC Main, AAC LC, AAC SSR, AAC LTP, AAC Scalable, TwinVQ, CELP, HVXC, TTSI, Main synthesis + MPEG4_SCALABLE_AUDIO_PROFILE, // AAC LC, AAC LTP, AAC Scalable, TwinVQ, CELP, HVXC, TTSI + MPEG4_SPEECH_AUDIO_PROFILE, // CELP, HVXC, TTSI + MPEG4_SYNTHETIC_AUDIO_PRIFILE, // TTSI, Main synthesis + MPEG4_HIGH_QUALITY_AUDIO_PROFILE, // AAC LC, AAC LTP, AAC Scalable, CELP, ER AAC LC, ER AAC LTP, ER AAC Scalable, ER CELP + MPEG4_LOW_DELAY_AUDIO_PROFILE, // CELP, HVXC, TTSI, ER AAC LD, ER CELP, ER HVXC + MPEG4_NATURAL_AUDIO_PRIFILE, // AAC Main, AAC LC, AAC SSR, AAC LTP, AAC Scalable, TwinVQ, CELP, HVXC, TTSI, ER AAC LC, ER AAC LTP, ER AAC Scalable, ER TwinVQ, ER BSAC, ER AAC LD, ER CELP, ER HVXC, ER HILN, ER Parametric + MPEG4_MOBILE_AUDIO_INTERNETWORKING_PROFILE, // ER AAC LC, ER AAC Scalable, ER TwinVQ, ER BSAC, ER AAC LD + MPEG4_HD_AAC_PROFILE, // AAC LC, SLS + MPEG4_ALS_SIMPLE_PROFILE, // ALS +}; + +enum mpeg4_aac_frequency +{ + MPEG4_AAC_96000 = 0, + MPEG4_AAC_88200, // 0x1 + MPEG4_AAC_64000, // 0x2 + MPEG4_AAC_48000, // 0x3 + MPEG4_AAC_44100, // 0x4 + MPEG4_AAC_32000, // 0x5 + MPEG4_AAC_24000, // 0x6 + MPEG4_AAC_22050, // 0x7 + MPEG4_AAC_16000, // 0x8 + MPEG4_AAC_12000, // 0x9 + MPEG4_AAC_11025, // 0xa + MPEG4_AAC_8000, // 0xb + MPEG4_AAC_7350, // 0xc + // reserved + // reserved + // escape value + +}; + +/// @return >=0-adts header length, <0-error +int mpeg4_aac_adts_save(const struct mpeg4_aac_t* aac, size_t payload, uint8_t* data, size_t bytes); +/// @return >=0-adts header length, <0-error +int mpeg4_aac_adts_load(const uint8_t* data, size_t bytes, struct mpeg4_aac_t* aac); + +/// @return >=0-audio specific config length, <0-error +int mpeg4_aac_audio_specific_config_load(const uint8_t* data, size_t bytes, struct mpeg4_aac_t* aac); +/// @return >=0-audio specific config length, <0-error +int mpeg4_aac_audio_specific_config_save(const struct mpeg4_aac_t* aac, uint8_t* data, size_t bytes); + +/// @return >=0-stream mux config length, <0-error +int mpeg4_aac_stream_mux_config_load(const uint8_t* data, size_t bytes, struct mpeg4_aac_t* aac); +/// @return >=0-stream mux config length, <0-error +int mpeg4_aac_stream_mux_config_save(const struct mpeg4_aac_t* aac, uint8_t* data, size_t bytes); + +/// @return >=0-length, <0-error +int mpeg4_aac_codecs(const struct mpeg4_aac_t* aac, char* codecs, size_t bytes); + +/// get AAC profile level indication value +int mpeg4_aac_profile_level(const struct mpeg4_aac_t* aac); + +/// MPEG4_AAC_96000 => 96000 +/// @return -1-error, other-frequency value +int mpeg4_aac_audio_frequency_to(enum mpeg4_aac_frequency index); +/// 96000 => MPEG4_AAC_96000 +/// @return -1-error, other-frequency index +int mpeg4_aac_audio_frequency_from(int frequency); + +/// @return aac channel count +uint8_t mpeg4_aac_channel_count(uint8_t channel_configuration); + +int mpeg4_aac_adts_frame_length(const uint8_t* data, size_t bytes); + +#if defined(__cplusplus) +} +#endif +#endif /* !_mpeg4_aac_h_ */ diff --git a/MediaServer/libflv/include/mpeg4-avc.h b/MediaServer/libflv/include/mpeg4-avc.h new file mode 100644 index 0000000..06b3795 --- /dev/null +++ b/MediaServer/libflv/include/mpeg4-avc.h @@ -0,0 +1,78 @@ +#ifndef _mpeg4_avc_h_ +#define _mpeg4_avc_h_ + +#include +#include + +#if defined(__cplusplus) +extern "C" { +#endif + +struct mpeg4_avc_t +{ +// uint8_t version; // 1-only + uint8_t profile; + uint8_t compatibility; // constraint_set[0-5]_flag + uint8_t level; + uint8_t nalu; // NALUnitLength = (lengthSizeMinusOne + 1), default 4(0x03+1) + + uint8_t nb_sps; + uint8_t nb_pps; + + struct mpeg4_avc_sps_t + { + uint16_t bytes; + uint8_t* data; + } sps[32]; // [0-31] + + struct mpeg4_avc_pps_t + { + uint16_t bytes; + uint8_t* data; + } pps[256]; + + // extension + uint8_t chroma_format_idc; + uint8_t bit_depth_luma_minus8; + uint8_t bit_depth_chroma_minus8; + + uint8_t data[4 * 1024]; + size_t off; +}; + +// load avc from AVCDecoderConfigurationRecord +/// @return <=0-error, >0-output bytes +int mpeg4_avc_decoder_configuration_record_load(const uint8_t* data, size_t bytes, struct mpeg4_avc_t* avc); + +/// @return <=0-error, >0-output bytes +int mpeg4_avc_decoder_configuration_record_save(const struct mpeg4_avc_t* avc, uint8_t* data, size_t bytes); + +// load avc from annex-b bitstream +int mpeg4_avc_from_nalu(const uint8_t* data, size_t bytes, struct mpeg4_avc_t* avc); + +int mpeg4_avc_to_nalu(const struct mpeg4_avc_t* avc, uint8_t* data, size_t bytes); + +int mpeg4_avc_codecs(const struct mpeg4_avc_t* avc, char* codecs, size_t bytes); + +/// @param[out] vcl 0-non VCL, 1-IDR, 2-P/B +/// @return <=0-error, >0-output bytes +int h264_annexbtomp4(struct mpeg4_avc_t* avc, const void* data, size_t bytes, void* out, size_t size, int* vcl, int* update); + +/// @return <=0-error, >0-output bytes +int h264_mp4toannexb(const struct mpeg4_avc_t* avc, const void* data, size_t bytes, void* out, size_t size); + +/// h264_is_new_access_unit H.264 new access unit(frame) +/// @return 1-new access, 0-not a new access +int h264_is_new_access_unit(const uint8_t* nalu, size_t bytes); + +/// H.264 nal unit split +int mpeg4_h264_annexb_nalu(const void* h264, size_t bytes, void (*handler)(void* param, const uint8_t* nalu, size_t bytes), void* param); + +/// Detect H.264 bitstrem type: H.264 Annexb or MP4-AVCC +/// @return 0-annexb, >0-avcc length, <0-error +int mpeg4_h264_bitstream_format(const uint8_t* h264, size_t bytes); + +#if defined(__cplusplus) +} +#endif +#endif /* !_mpeg4_avc_h_ */ diff --git a/MediaServer/libflv/include/mpeg4-bits.h b/MediaServer/libflv/include/mpeg4-bits.h new file mode 100644 index 0000000..4c5343d --- /dev/null +++ b/MediaServer/libflv/include/mpeg4-bits.h @@ -0,0 +1,201 @@ +#ifndef _mpeg4_bits_h_ +#define _mpeg4_bits_h_ + +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +struct mpeg4_bits_t +{ + uint8_t* data; + size_t size; + size_t bits; // offset bit + int error; +}; + +#define mpeg4_bits_read_uint8(bits, n) (uint8_t)mpeg4_bits_read_n(bits, n) +#define mpeg4_bits_read_uint16(bits, n) (uint16_t)mpeg4_bits_read_n(bits, n) +#define mpeg4_bits_read_uint32(bits, n) (uint32_t)mpeg4_bits_read_n(bits, n) +#define mpeg4_bits_read_uint64(bits, n) (uint64_t)mpeg4_bits_read_n(bits, n) +#define mpeg4_bits_write_uint8(bits, v, n) mpeg4_bits_write_n(bits, (uint64_t)v, n) +#define mpeg4_bits_write_uint16(bits, v, n) mpeg4_bits_write_n(bits, (uint64_t)v, n) +#define mpeg4_bits_write_uint32(bits, v, n) mpeg4_bits_write_n(bits, (uint64_t)v, n) +#define mpeg4_bits_write_uint64(bits, v, n) mpeg4_bits_write_n(bits, (uint64_t)v, n) + +static inline void mpeg4_bits_init(struct mpeg4_bits_t* bits, void* data, size_t size) +{ + bits->data = (uint8_t*)data; + bits->size = size; + bits->bits = 0; + bits->error = 0; +} + +/// @return 1-error, 0-no error +static inline int mpeg4_bits_error(struct mpeg4_bits_t* bits) +{ + //return bits->bits >= bits->size * 8 ? 1 : 0; + return bits->error; +} + +static inline void mpeg4_bits_aligment(struct mpeg4_bits_t* bits, int n) +{ + bits->bits = (bits->bits + n - 1) / n * n; +} + +static inline size_t mpeg4_bits_remain(struct mpeg4_bits_t* bits) +{ + return bits->error ? 0 : (bits->size * 8 - bits->bits); +} + +static inline void mpeg4_bits_skip(struct mpeg4_bits_t* bits, size_t n) +{ + bits->bits += n; + if (bits->bits > bits->size * 8) + { + bits->error = -1; + } +} + +/// read 1-bit from bit stream(offset position) +/// @param[in] bits bit stream +/// @return -1-error, 1-value, 0-value +static inline int mpeg4_bits_read(struct mpeg4_bits_t* bits) +{ + uint8_t bit; + assert(bits && bits->data && bits->size > 0); + if (bits->bits >= bits->size * 8) + { + bits->error = -1; + return 0; // throw exception + } + + bit = bits->data[bits->bits/8] & (0x80U >> (bits->bits%8)); + bits->bits += 1; // update offset + return bit ? 1 : 0; +} + +/// read n-bit(n <= 64) from bit stream(offset position) +/// @param[in] bits bit stream +/// @return -1-error, other-value +static inline uint64_t mpeg4_bits_read_n(struct mpeg4_bits_t* bits, int n) +{ + int m; + size_t i; + uint64_t v; + + assert(n > 0 && n <= 64); + assert(bits && bits->data && bits->size > 0); + if (bits->bits + n > bits->size * 8 || n > 64 || n < 0) + { + bits->error = -1; + return 0; // throw exception + } + + m = n; + v = bits->data[bits->bits / 8] & (0xFFU >> (bits->bits%8)); // remain valid value + if (n <= 8 - (int)(bits->bits % 8)) + { + v = v >> (8 - (bits->bits % 8) - n); // shift right value + bits->bits += n; + return v; + } + + n -= 8 - (int)(bits->bits % 8); + for (i = 1; n >= 8; i++) + { + assert(bits->bits / 8 + i < bits->size); + v <<= 8; + v += bits->data[bits->bits / 8 + i]; + n -= 8; + } + + if (n > 0) + { + v <<= n; + v += bits->data[bits->bits / 8 + i] >> (8 - n); + } + + bits->bits += m; + return v; +} + +// http://aomedia.org/av1/specification/conventions/#descriptors +static inline uint64_t mpeg4_bits_read_uvlc(struct mpeg4_bits_t* bits) +{ + uint64_t value; + int leadingZeros; + for (leadingZeros = 0; !mpeg4_bits_read(bits); ++leadingZeros) + { + } + + if (leadingZeros >= 32) + return (1ULL << 32) - 1; + + value = mpeg4_bits_read_n(bits, leadingZeros); + return (1ULL << leadingZeros) - 1 + value; +} + +static inline uint64_t mpeg4_bits_read_latm(struct mpeg4_bits_t* bits) +{ + int len; + len = (int)mpeg4_bits_read_n(bits, 2); + return mpeg4_bits_read_n(bits, (len + 1) * 8); +} + +/// write 1-bit +/// @param[in] v write 0 if v value 0, other, write 1 +static inline int mpeg4_bits_write(struct mpeg4_bits_t* bits, int v) +{ + assert(bits && bits->data && bits->size > 0); + if (bits->bits >= bits->size * 8) + { + bits->error = -1; + return -1; // throw exception + } + + if(v) + bits->data[bits->bits / 8] |= (0x80U >> (bits->bits % 8)); + bits->bits += 1; // update offset + return 0; +} + +static inline int mpeg4_bits_write_n(struct mpeg4_bits_t* bits, uint64_t v, int n) +{ + int m; + size_t i; + + assert(n > 0 && n <= 64); + assert(bits && bits->data && bits->size > 0); + if (bits->bits + n > bits->size * 8 || n > 64 || n < 0) + { + bits->error = -1; + return -1; // throw exception + } + + m = n; + v = v << (64 - n); // left shift to first bit + + bits->data[bits->bits / 8] |= v >> (56 + (bits->bits % 8)); // remain valid value + v <<= 8 - (bits->bits % 8); + n -= 8 - (int)(bits->bits % 8); + + for (i = 1; n > 0; i++) + { + assert(bits->bits / 8 + i < bits->size); + bits->data[bits->bits / 8 + i] = (uint8_t)(v >> 56); + v <<= 8; + n -= 8; + } + + bits->bits += m; + return 0; +} + +#ifdef __cplusplus +} +#endif +#endif /* !_mpeg4_bits_h_ */ diff --git a/MediaServer/libflv/include/mpeg4-hevc.h b/MediaServer/libflv/include/mpeg4-hevc.h new file mode 100644 index 0000000..f95e4d6 --- /dev/null +++ b/MediaServer/libflv/include/mpeg4-hevc.h @@ -0,0 +1,68 @@ +#ifndef _mpeg4_hevc_h_ +#define _mpeg4_hevc_h_ + +#include +#include + +#if defined(__cplusplus) +extern "C" { +#endif + +struct mpeg4_hevc_t +{ + uint8_t configurationVersion; // 1-only + uint8_t general_profile_space; // 2bit,[0,3] + uint8_t general_tier_flag; // 1bit,[0,1] + uint8_t general_profile_idc; // 5bit,[0,31] + uint32_t general_profile_compatibility_flags; + uint64_t general_constraint_indicator_flags; + uint8_t general_level_idc; + uint16_t min_spatial_segmentation_idc; + uint8_t parallelismType; // 2bit,[0,3] + uint8_t chromaFormat; // 2bit,[0,3] + uint8_t bitDepthLumaMinus8; // 3bit,[0,7] + uint8_t bitDepthChromaMinus8; // 3bit,[0,7] + uint16_t avgFrameRate; + uint8_t constantFrameRate; // 2bit,[0,3] + uint8_t numTemporalLayers; // 3bit,[0,7] + uint8_t temporalIdNested; // 1bit,[0,1] + uint8_t lengthSizeMinusOne; // 2bit,[0,3] + + uint8_t numOfArrays; + struct + { + uint8_t array_completeness; + uint8_t type; // nalu type + uint16_t bytes; + uint8_t* data; + } nalu[64]; + + uint8_t array_completeness; + uint8_t data[4 * 1024]; + size_t off; +}; + +// load hevc from HEVCDecoderConfigurationRecord +int mpeg4_hevc_decoder_configuration_record_load(const uint8_t* data, size_t bytes, struct mpeg4_hevc_t* hevc); + +int mpeg4_hevc_decoder_configuration_record_save(const struct mpeg4_hevc_t* hevc, uint8_t* data, size_t bytes); + +// load hevc from annex-b bitstream +int mpeg4_hevc_from_nalu(const uint8_t* data, size_t bytes, struct mpeg4_hevc_t* hevc); + +int mpeg4_hevc_to_nalu(const struct mpeg4_hevc_t* hevc, uint8_t* data, size_t bytes); + +int mpeg4_hevc_codecs(const struct mpeg4_hevc_t* hevc, char* codecs, size_t bytes); + +int h265_annexbtomp4(struct mpeg4_hevc_t* hevc, const void* data, size_t bytes, void* out, size_t size, int *vcl, int* update); + +int h265_mp4toannexb(const struct mpeg4_hevc_t* hevc, const void* data, size_t bytes, void* out, size_t size); + +/// h265_is_new_access_unit H.265 new access unit(frame) +/// @return 1-new access, 0-not a new access +int h265_is_new_access_unit(const uint8_t* nalu, size_t bytes); + +#if defined(__cplusplus) +} +#endif +#endif /* !_mpeg4_hevc_h_ */ diff --git a/MediaServer/libflv/include/mpeg4-vvc.h b/MediaServer/libflv/include/mpeg4-vvc.h new file mode 100644 index 0000000..d025df6 --- /dev/null +++ b/MediaServer/libflv/include/mpeg4-vvc.h @@ -0,0 +1,73 @@ +#ifndef _mpeg_vvc_h +#define _mpeg_vvc_h + +#include +#include + +#if defined(__cplusplus) +extern "C" { +#endif + +struct mpeg4_vvc_t +{ + uint32_t lengthSizeMinusOne : 2; // 2bit,[0,3] + uint32_t ptl_present_flag : 1; + + // valid on ptl_present_flag + uint32_t ols_idx : 9; + uint32_t num_sublayers : 3; + uint32_t constant_frame_rate : 2; + uint32_t chroma_format_idc : 2; + uint32_t bit_depth_minus8 : 2; + uint16_t max_picture_width; + uint16_t max_picture_height; + uint16_t avg_frame_rate; + struct + { + uint32_t num_bytes_constraint_info : 6; + uint32_t general_profile_idc : 7; + uint32_t general_tier_flag : 1; + uint32_t general_level_idc : 8; + uint32_t ptl_frame_only_constraint_flag : 1; + uint32_t ptl_multi_layer_enabled_flag : 1; + uint32_t ptl_sublayer_level_present_flag : 8; + uint8_t general_constraint_info[64]; + uint8_t sublayer_level_idc[8 - 2]; + uint8_t ptl_num_sub_profiles; + uint32_t *general_sub_profile_idc; // --> data + } native_ptl; + + uint8_t numOfArrays; + struct + { + uint8_t array_completeness; + uint8_t type; // nalu type + uint16_t bytes; + uint8_t* data; + } nalu[64]; + + uint8_t array_completeness; + uint8_t data[4 * 1024]; + size_t off; +}; + +int mpeg4_vvc_decoder_configuration_record_load(const uint8_t* data, size_t bytes, struct mpeg4_vvc_t* vvc); + +int mpeg4_vvc_decoder_configuration_record_save(const struct mpeg4_vvc_t* vvc, uint8_t* data, size_t bytes); + +int mpeg4_vvc_to_nalu(const struct mpeg4_vvc_t* vvc, uint8_t* data, size_t bytes); + +int mpeg4_vvc_codecs(const struct mpeg4_vvc_t* vvc, char* codecs, size_t bytes); + +int h266_annexbtomp4(struct mpeg4_vvc_t* vvc, const void* data, size_t bytes, void* out, size_t size, int* vcl, int* update); + +int h266_mp4toannexb(const struct mpeg4_vvc_t* vvc, const void* data, size_t bytes, void* out, size_t size); + +/// h266_is_new_access_unit H.266 new access unit(frame) +/// @return 1-new access, 0-not a new access +int h266_is_new_access_unit(const uint8_t* nalu, size_t bytes); + +#if defined(__cplusplus) +} +#endif +#endif /* !_mpeg_vvc_h */ diff --git a/MediaServer/libflv/include/opus-head.h b/MediaServer/libflv/include/opus-head.h new file mode 100644 index 0000000..630ad54 --- /dev/null +++ b/MediaServer/libflv/include/opus-head.h @@ -0,0 +1,39 @@ +#ifndef _opus_head_h_ +#define _opus_head_h_ + +#include +#include + +#if defined(__cplusplus) +extern "C" { +#endif + +struct opus_head_t +{ + uint8_t version; + uint8_t channels; + uint16_t pre_skip; + uint32_t input_sample_rate; + int16_t output_gain; + uint8_t channel_mapping_family; + uint8_t stream_count; + uint8_t coupled_count; + uint8_t channel_mapping[8]; +}; + +/// @return >0-ok, <=0-error +int opus_head_save(const struct opus_head_t* opus, uint8_t* data, size_t bytes); +/// @return >0-ok, <=0-error +int opus_head_load(const uint8_t* data, size_t bytes, struct opus_head_t* opus); + +static inline int opus_head_channels(const struct opus_head_t* opus) +{ + return 0 == opus->channels ? 2 : opus->channels; +} + +int opus_packet_getframes(const void* data, size_t len, int (*onframe)(uint8_t toc, const void* frame, size_t size), void* param); + +#if defined(__cplusplus) +} +#endif +#endif /* !_opus_head_h_ */ diff --git a/MediaServer/libflv/include/riff-acm.h b/MediaServer/libflv/include/riff-acm.h new file mode 100644 index 0000000..5e1b282 --- /dev/null +++ b/MediaServer/libflv/include/riff-acm.h @@ -0,0 +1,43 @@ +#ifndef _riff_acm_h_ +#define _riff_acm_h_ + +#include +#include + +#ifndef WAVE_FORMAT_PCM +#define WAVE_FORMAT_PCM 1 +#define WAVE_FORMAT_ADPCM 2 +#define WAVE_FORMAT_ALAW 6 +#define WAVE_FORMAT_MULAW 7 +#endif + +#if defined(__cplusplus) +extern "C" { +#endif + +#pragma pack(push) +#pragma pack(1) +struct wave_format_t +{ + uint16_t wFormatTag; + uint16_t nChannels; + uint32_t nSamplesPerSec; + uint32_t nAvgBytesPerSec; + uint16_t nBlockAlign; + uint16_t wBitsPerSample; + uint16_t cbSize; + + // WAVEFORMATEXTENSIBLE(only cbSize > 0) + uint16_t Samples; + uint32_t dwChannelMask; + uint8_t SubFormat[16]; +}; +#pragma pack(pop) + +int wave_format_load(const uint8_t* data, int bytes, struct wave_format_t* wav); +int wave_format_save(const struct wave_format_t* wav, uint8_t* data, int bytes); + +#if defined(__cplusplus) +} +#endif +#endif /* !_riff_acm_h_ */ diff --git a/MediaServer/libflv/include/webm-vpx.h b/MediaServer/libflv/include/webm-vpx.h new file mode 100644 index 0000000..6808257 --- /dev/null +++ b/MediaServer/libflv/include/webm-vpx.h @@ -0,0 +1,35 @@ +#ifndef _webm_vpx_h_ +#define _webm_vpx_h_ + +#include +#include + +#if defined(__cplusplus) +extern "C" { +#endif + +// VP8/VP9/VP10 +struct webm_vpx_t +{ + uint8_t profile; + uint8_t level; + uint8_t bit_depth; + uint8_t chroma_subsampling; // 0-4:2:0 vertical, 1-4:2:0 colocated with luma (0,0), 2-4:2:2, 3-4:4:4 + uint8_t video_full_range_flag; // 0 = legal range (e.g. 16-235 for 8 bit sample depth); 1 = full range (e.g. 0-255 for 8-bit sample depth) + uint8_t colour_primaries; // ISO/IEC 23001-8:2016 + uint8_t transfer_characteristics; + uint8_t matrix_coefficients; + uint16_t codec_intialization_data_size; // must be 0 + uint8_t codec_intialization_data[1]; // not used for VP8 and VP9 +}; + +int webm_vpx_codec_configuration_record_load(const uint8_t* data, size_t bytes, struct webm_vpx_t* vpx); +int webm_vpx_codec_configuration_record_save(const struct webm_vpx_t* vpx, uint8_t* data, size_t bytes); + +int webm_vpx_codec_configuration_record_from_vp8(struct webm_vpx_t* vpx, int* width, int* height, const void* keyframe, size_t bytes); +int webm_vpx_codec_configuration_record_from_vp9(struct webm_vpx_t* vpx, int* width, int* height, const void* keyframe, size_t bytes); + +#if defined(__cplusplus) +} +#endif +#endif /* !_webm_vpx_h_ */ diff --git a/MediaServer/libflv/source/amf0.c b/MediaServer/libflv/source/amf0.c new file mode 100644 index 0000000..333cc39 --- /dev/null +++ b/MediaServer/libflv/source/amf0.c @@ -0,0 +1,591 @@ +#include "amf0.h" +#include +#include +#include +#include + +static double s_double = 1.0; // 3ff0 0000 0000 0000 + +static uint8_t* AMFWriteInt16(uint8_t* ptr, const uint8_t* end, uint16_t value) +{ + if (ptr + 2 > end) return NULL; + ptr[0] = value >> 8; + ptr[1] = value & 0xFF; + return ptr + 2; +} + +static uint8_t* AMFWriteInt32(uint8_t* ptr, const uint8_t* end, uint32_t value) +{ + if (ptr + 4 > end) return NULL; + ptr[0] = (uint8_t)(value >> 24); + ptr[1] = (uint8_t)(value >> 16); + ptr[2] = (uint8_t)(value >> 8); + ptr[3] = (uint8_t)(value & 0xFF); + return ptr + 4; +} + +static uint8_t* AMFWriteString16(uint8_t* ptr, const uint8_t* end, const char* string, size_t length) +{ + if (ptr + 2 + length > end) return NULL; + ptr = AMFWriteInt16(ptr, end, (uint16_t)length); + memcpy(ptr, string, length); + return ptr + length; +} + +static uint8_t* AMFWriteString32(uint8_t* ptr, const uint8_t* end, const char* string, size_t length) +{ + if (ptr + 4 + length > end) return NULL; + ptr = AMFWriteInt32(ptr, end, (uint32_t)length); + memcpy(ptr, string, length); + return ptr + length; +} + +uint8_t* AMFWriteNull(uint8_t* ptr, const uint8_t* end) +{ + if (!ptr || ptr + 1 > end) return NULL; + + *ptr++ = AMF_NULL; + return ptr; +} + +uint8_t* AMFWriteUndefined(uint8_t* ptr, const uint8_t* end) +{ + if (!ptr || ptr + 1 > end) return NULL; + + *ptr++ = AMF_UNDEFINED; + return ptr; +} + +uint8_t* AMFWriteObject(uint8_t* ptr, const uint8_t* end) +{ + if (!ptr || ptr + 1 > end) return NULL; + + *ptr++ = AMF_OBJECT; + return ptr; +} + +uint8_t* AMFWriteObjectEnd(uint8_t* ptr, const uint8_t* end) +{ + if (!ptr || ptr + 3 > end) return NULL; + + /* end of object - 0x00 0x00 0x09 */ + *ptr++ = 0; + *ptr++ = 0; + *ptr++ = AMF_OBJECT_END; + return ptr; +} + +uint8_t* AMFWriteTypedObject(uint8_t* ptr, const uint8_t* end) +{ + if (!ptr || ptr + 1 > end) return NULL; + + *ptr++ = AMF_TYPED_OBJECT; + return ptr; +} + +uint8_t* AMFWriteECMAArarry(uint8_t* ptr, const uint8_t* end) +{ + if (!ptr || ptr + 1 > end) return NULL; + + *ptr++ = AMF_ECMA_ARRAY; + return AMFWriteInt32(ptr, end, 0); // U32 associative-count +} + +uint8_t* AMFWriteBoolean(uint8_t* ptr, const uint8_t* end, uint8_t value) +{ + if (!ptr || ptr + 2 > end) return NULL; + + ptr[0] = AMF_BOOLEAN; + ptr[1] = 0 == value ? 0 : 1; + return ptr + 2; +} + +uint8_t* AMFWriteDouble(uint8_t* ptr, const uint8_t* end, double value) +{ + if (!ptr || ptr + 9 > end) return NULL; + + assert(8 == sizeof(double)); + *ptr++ = AMF_NUMBER; + + // Little-Endian + if (0x00 == *(char*)&s_double) + { + *ptr++ = ((uint8_t*)&value)[7]; + *ptr++ = ((uint8_t*)&value)[6]; + *ptr++ = ((uint8_t*)&value)[5]; + *ptr++ = ((uint8_t*)&value)[4]; + *ptr++ = ((uint8_t*)&value)[3]; + *ptr++ = ((uint8_t*)&value)[2]; + *ptr++ = ((uint8_t*)&value)[1]; + *ptr++ = ((uint8_t*)&value)[0]; + } + else + { + memcpy(ptr, &value, 8); + } + return ptr; +} + +uint8_t* AMFWriteString(uint8_t* ptr, const uint8_t* end, const char* string, size_t length) +{ + if (!ptr || ptr + 1 + (length < 65536 ? 2 : 4) + length > end || length > UINT32_MAX) + return NULL; + + if (length < 65536) + { + *ptr++ = AMF_STRING; + AMFWriteString16(ptr, end, string, length); + ptr += 2; + } + else + { + *ptr++ = AMF_LONG_STRING; + AMFWriteString32(ptr, end, string, length); + ptr += 4; + } + return ptr + length; +} + +uint8_t* AMFWriteDate(uint8_t* ptr, const uint8_t* end, double milliseconds, int16_t timezone) +{ + if (!ptr || ptr + 11 > end) + return NULL; + + AMFWriteDouble(ptr, end, milliseconds); + *ptr = AMF_DATE; // rewrite to date + return AMFWriteInt16(ptr + 8, end, timezone); +} + +uint8_t* AMFWriteNamedBoolean(uint8_t* ptr, const uint8_t* end, const char* name, size_t length, uint8_t value) +{ + if (ptr + length + 2 + 2 > end) + return NULL; + + ptr = AMFWriteString16(ptr, end, name, length); + return ptr ? AMFWriteBoolean(ptr, end, value) : NULL; +} + +uint8_t* AMFWriteNamedDouble(uint8_t* ptr, const uint8_t* end, const char* name, size_t length, double value) +{ + if (ptr + length + 2 + 8 + 1 > end) + return NULL; + + ptr = AMFWriteString16(ptr, end, name, length); + return ptr ? AMFWriteDouble(ptr, end, value) : NULL; +} + +uint8_t* AMFWriteNamedString(uint8_t* ptr, const uint8_t* end, const char* name, size_t length, const char* value, size_t length2) +{ + if (ptr + length + 2 + length2 + 3 > end) + return NULL; + + ptr = AMFWriteString16(ptr, end, name, length); + return ptr ? AMFWriteString(ptr, end, value, length2) : NULL; +} + +static const uint8_t* AMFReadInt16(const uint8_t* ptr, const uint8_t* end, uint32_t* value) +{ + if (!ptr || ptr + 2 > end) + return NULL; + + if (value) + { + *value = ((uint32_t)ptr[0] << 8) | ptr[1]; + } + return ptr + 2; +} + +static const uint8_t* AMFReadInt32(const uint8_t* ptr, const uint8_t* end, uint32_t* value) +{ + if (!ptr || ptr + 4 > end) + return NULL; + + if (value) + { + *value = ((uint32_t)ptr[0] << 24) | ((uint32_t)ptr[1] << 16) | ((uint32_t)ptr[2] << 8) | ptr[3]; + } + return ptr + 4; +} + +const uint8_t* AMFReadNull(const uint8_t* ptr, const uint8_t* end) +{ + (void)end; + return ptr; +} + +const uint8_t* AMFReadUndefined(const uint8_t* ptr, const uint8_t* end) +{ + (void)end; + return ptr; +} + +const uint8_t* AMFReadBoolean(const uint8_t* ptr, const uint8_t* end, uint8_t* value) +{ + if (!ptr || ptr + 1 > end) + return NULL; + + if (value) + { + *value = ptr[0]; + } + return ptr + 1; +} + +const uint8_t* AMFReadDouble(const uint8_t* ptr, const uint8_t* end, double* value) +{ + uint8_t* p = (uint8_t*)value; + if (!ptr || ptr + 8 > end) + return NULL; + + if (value) + { + if (0x00 == *(char*)&s_double) + {// Little-Endian + *p++ = ptr[7]; + *p++ = ptr[6]; + *p++ = ptr[5]; + *p++ = ptr[4]; + *p++ = ptr[3]; + *p++ = ptr[2]; + *p++ = ptr[1]; + *p++ = ptr[0]; + } + else + { + memcpy(value, ptr, 8); + } + } + return ptr + 8; +} + +const uint8_t* AMFReadString(const uint8_t* ptr, const uint8_t* end, int isLongString, char* string, size_t length) +{ + uint32_t len = 0; + if (0 == isLongString) + ptr = AMFReadInt16(ptr, end, &len); + else + ptr = AMFReadInt32(ptr, end, &len); + + if (!ptr || ptr + len > end) + return NULL; + + if (string && length > len) + { + memcpy(string, ptr, len); + string[len] = 0; + } + return ptr + len; +} + +const uint8_t* AMFReadDate(const uint8_t* ptr, const uint8_t* end, double *milliseconds, int16_t *timezone) +{ + uint32_t v; + ptr = AMFReadDouble(ptr, end, milliseconds); + if (ptr) + { + ptr = AMFReadInt16(ptr, end, &v); + if(timezone) + *timezone = (int16_t)v; + } + return ptr; +} + +static const uint8_t* amf_read_object(const uint8_t* data, const uint8_t* end, struct amf_object_item_t* items, size_t n); +static const uint8_t* amf_read_ecma_array(const uint8_t* data, const uint8_t* end, struct amf_object_item_t* items, size_t n); +static const uint8_t* amf_read_strict_array(const uint8_t* ptr, const uint8_t* end, struct amf_object_item_t* items, size_t n); + +static const uint8_t* amf_read_item(const uint8_t* data, const uint8_t* end, enum AMFDataType type, struct amf_object_item_t* item) +{ + switch (type) + { + case AMF_BOOLEAN: + return AMFReadBoolean(data, end, (uint8_t*)(item ? item->value : NULL)); + + case AMF_NUMBER: + return AMFReadDouble(data, end, (double*)(item ? item->value : NULL)); + + case AMF_STRING: + return AMFReadString(data, end, 0, (char*)(item ? item->value : NULL), item ? item->size : 0); + + case AMF_LONG_STRING: + return AMFReadString(data, end, 1, (char*)(item ? item->value : NULL), item ? item->size : 0); + + case AMF_DATE: + return AMFReadDate(data, end, (double*)(item ? item->value : NULL), (int16_t*)(item ? (char*)item->value + 8 : NULL)); + + case AMF_OBJECT: + return amf_read_object(data, end, (struct amf_object_item_t*)(item ? item->value : NULL), item ? item->size : 0); + + case AMF_NULL: + return data; + + case AMF_UNDEFINED: + return data; + + case AMF_ECMA_ARRAY: + return amf_read_ecma_array(data, end, (struct amf_object_item_t*)(item ? item->value : NULL), item ? item->size : 0); + + case AMF_STRICT_ARRAY: + return amf_read_strict_array(data, end, (struct amf_object_item_t*)(item ? item->value : NULL), item ? item->size : 0); + + default: + assert(0); + return NULL; + } +} + +static inline int amf_read_item_type_check(uint8_t type0, uint8_t itemtype) +{ + // decode AMF_ECMA_ARRAY as AMF_OBJECT + return (type0 == itemtype || (AMF_OBJECT == itemtype && (AMF_ECMA_ARRAY == type0 || AMF_NULL == type0))) ? 1 : 0; +} + +static const uint8_t* amf_read_strict_array(const uint8_t* ptr, const uint8_t* end, struct amf_object_item_t* items, size_t n) +{ + uint8_t type; + uint32_t i, count; + if (!ptr || ptr + 4 > end) + return NULL; + + ptr = AMFReadInt32(ptr, end, &count); // U32 array-count + for (i = 0; i < count && ptr && ptr < end; i++) + { + type = *ptr++; + ptr = amf_read_item(ptr, end, type, (i < n && amf_read_item_type_check(type, items[i].type)) ? &items[i] : NULL); + } + + return ptr; +} + +static const uint8_t* amf_read_ecma_array(const uint8_t* ptr, const uint8_t* end, struct amf_object_item_t* items, size_t n) +{ + if (!ptr || ptr + 4 > end) + return NULL; + ptr += 4; // U32 associative-count + return amf_read_object(ptr, end, items, n); +} + +static const uint8_t* amf_read_object(const uint8_t* data, const uint8_t* end, struct amf_object_item_t* items, size_t n) +{ + uint8_t type; + uint32_t len; + size_t i; + + while (data && data + 2 <= end) + { + len = *data++ << 8; + len |= *data++; + if (0 == len) + break; // last item + + if (data + len + 1 > end) + return NULL; // invalid + + for (i = 0; i < n; i++) + { + if (strlen(items[i].name) == len && 0 == memcmp(items[i].name, data, len) && amf_read_item_type_check(data[len], items[i].type)) + break; + } + + data += len; // skip name string + type = *data++; // value type + data = amf_read_item(data, end, type, i < n ? &items[i] : NULL); + } + + if (data && data < end && AMF_OBJECT_END == *data) + return data + 1; + return NULL; // invalid object +} + +const uint8_t* amf_read_items(const uint8_t* data, const uint8_t* end, struct amf_object_item_t* items, size_t count) +{ + size_t i; + uint8_t type; + for (i = 0; i < count && data && data < end; i++) + { + type = *data++; + if (!amf_read_item_type_check(type, items[i].type)) + return NULL; + + data = amf_read_item(data, end, type, &items[i]); + } + + return data; +} + +#if defined(_DEBUG) || defined(DEBUG) +struct rtmp_amf0_command_t +{ + char fmsVer[64]; + double capabilities; + double mode; +}; +struct rtmp_amf0_data_t +{ + char version[64]; +}; +struct rtmp_amf0_information_t +{ + char code[64]; // NetStream.Play.Start + char level[8]; // warning/status/error + char description[256]; + double clientid; + double objectEncoding; + struct rtmp_amf0_data_t data; +}; +static void amf0_test_1(void) +{ + const uint8_t amf0[] = { + 0x02, 0x00, 0x07, 0x5F, 0x72, 0x65, 0x73, 0x75, 0x6C, 0x74, + 0x00, 0x3F, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + + 0x03, + 0x00, 0x06, 0x66, 0x6D, 0x73, 0x56, 0x65, 0x72, 0x02, 0x00, 0x0E, 0x46, 0x4D, 0x53, 0x2F, 0x33, 0x2C, 0x35, 0x2C, 0x35, 0x2C, 0x32, 0x30, 0x30, 0x34, + 0x00, 0x0C, 0x63, 0x61, 0x70,0x61, 0x62, 0x69, 0x6C, 0x69, 0x74, 0x69, 0x65, 0x73, 0x00, 0x40, 0x3F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x04, 0x6D, 0x6F, 0x64, 0x65, 0x00, 0x3F, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x09, + + 0x03, + 0x00, 0x05, 0x6C, 0x65, 0x76, 0x65, 0x6C, 0x02, 0x00, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, + 0x00, 0x04, 0x63, 0x6F, 0x64, 0x65, 0x02, 0x00, 0x1D, 0x4E, 0x65, 0x74, 0x43, 0x6F, 0x6E, 0x6E, 0x65, 0x63, 0x74, 0x69, 0x6F, 0x6E, 0x2E, 0x43, 0x6F, 0x6E, 0x6E, 0x65, 0x63, 0x74, 0x2E, 0x53, 0x75, 0x63, 0x63, 0x65, 0x73, 0x73, + 0x00, 0x0B, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6F, 0x6E, 0x02, 0x00, 0x15, 0x43, 0x6F, 0x6E, 0x6E, 0x65, 0x63, 0x74, 0x69, 0x6F, 0x6E, 0x20, 0x73, 0x75, 0x63, 0x63, 0x65, 0x65, 0x64, 0x65, 0x64, 0x2E, + 0x00, 0x04, 0x64, 0x61, 0x74, 0x61, + 0x08, 0x00, 0x00, 0x00, 0x01, + 0x00, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6F, 0x6E, 0x02, 0x00, 0x0A, 0x33, 0x2C, 0x35, 0x2C, 0x35, 0x2C, 0x32, 0x30, 0x30, 0x34, + 0x00, 0x00, 0x09, + 0x00, 0x08, 0x63, 0x6C, 0x69, 0x65, 0x6E, 0x74, 0x69, 0x64, 0x00, 0x41, 0xD7, 0x9B, 0x78, 0x7C, 0xC0, 0x00, 0x00, + 0x00, 0x0E, 0x6F, 0x62, 0x6A, 0x65, 0x63, 0x74, 0x45, 0x6E, 0x63, 0x6F, 0x64, 0x69, 0x6E, 0x67, 0x00, 0x40, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x09, + }; + + char reply[8]; + const uint8_t* end; + double transactionId; + struct rtmp_amf0_command_t fms; + struct rtmp_amf0_information_t result; + struct amf_object_item_t cmd[3]; + struct amf_object_item_t data[1]; + struct amf_object_item_t info[6]; + struct amf_object_item_t items[4]; + +#define AMF_OBJECT_ITEM_VALUE(v, amf_type, amf_name, amf_value, amf_size) { v.type=amf_type; v.name=amf_name; v.value=amf_value; v.size=amf_size; } + AMF_OBJECT_ITEM_VALUE(cmd[0], AMF_STRING, "fmsVer", fms.fmsVer, sizeof(fms.fmsVer)); + AMF_OBJECT_ITEM_VALUE(cmd[1], AMF_NUMBER, "capabilities", &fms.capabilities, sizeof(fms.capabilities)); + AMF_OBJECT_ITEM_VALUE(cmd[2], AMF_NUMBER, "mode", &fms.mode, sizeof(fms.mode)); + + AMF_OBJECT_ITEM_VALUE(data[0], AMF_STRING, "version", result.data.version, sizeof(result.data.version)); + + AMF_OBJECT_ITEM_VALUE(info[0], AMF_STRING, "code", result.code, sizeof(result.code)); + AMF_OBJECT_ITEM_VALUE(info[1], AMF_STRING, "level", result.level, sizeof(result.level)); + AMF_OBJECT_ITEM_VALUE(info[2], AMF_STRING, "description", result.description, sizeof(result.description)); + AMF_OBJECT_ITEM_VALUE(info[3], AMF_ECMA_ARRAY, "data", data, sizeof(data)/sizeof(data[0])); + AMF_OBJECT_ITEM_VALUE(info[4], AMF_NUMBER, "clientid", &result.clientid, sizeof(result.clientid)); + AMF_OBJECT_ITEM_VALUE(info[5], AMF_NUMBER, "objectEncoding", &result.objectEncoding, sizeof(result.objectEncoding)); + + AMF_OBJECT_ITEM_VALUE(items[0], AMF_STRING, "reply", reply, sizeof(reply)); // Command object + AMF_OBJECT_ITEM_VALUE(items[1], AMF_NUMBER, "transaction", &transactionId, sizeof(transactionId)); // Command object + AMF_OBJECT_ITEM_VALUE(items[2], AMF_OBJECT, "command", cmd, sizeof(cmd)/sizeof(cmd[0])); // Command object + AMF_OBJECT_ITEM_VALUE(items[3], AMF_OBJECT, "information", info, sizeof(info) / sizeof(info[0])); // Information object + + end = amf0 + sizeof(amf0); + assert(end == amf_read_items(amf0, end, items, sizeof(items) / sizeof(items[0]))); + assert(0 == strcmp(fms.fmsVer, "FMS/3,5,5,2004")); + assert(fms.capabilities == 31.0); + assert(fms.mode == 1.0); + assert(0 == strcmp(result.code, "NetConnection.Connect.Success")); + assert(0 == strcmp(result.level, "status")); + assert(0 == strcmp(result.description, "Connection succeeded.")); + assert(0 == strcmp(result.data.version, "3,5,5,2004")); + assert(1584259571.0 == result.clientid); + assert(3.0 == result.objectEncoding); +} + +struct rtmp_amf0_connect_t +{ + char app[64]; // Server application name, e.g.: testapp + char flashver[32]; // Flash Player version, FMSc/1.0 + char swfUrl[256]; // URL of the source SWF file + char tcUrl[256]; // URL of the Server, rtmp://host:1935/testapp/instance1 + uint8_t fpad; // boolean: True if proxy is being used. + double capabilities; // double default: 15 + double audioCodecs; // double default: 4071 + double videoCodecs; // double default: 252 + double videoFunction; // double default: 1 + double encoding; + char pageUrl[256]; // http://host/sample.html +}; + +static void amf0_test_2(void) +{ + const uint8_t amf0[] = { + 0x02, 0x00, 0x07, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x00, 0x3f, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x03, 0x00, + 0x03, 0x61, 0x70, 0x70, + 0x02, + 0x00, 0x05, 0x6c, 0x69, 0x76, 0x65, 0x2f, + 0x00, 0x05, 0x74, 0x63, 0x55, 0x72, 0x6c, + 0x02, 0x00, 0x1A, 0x72, 0x74, 0x6d, 0x70, 0x3a, 0x2f, 0x2f, 0x70, 0x75, 0x73, 0x68, 0x2e, 0x72, 0x74, 0x6d, 0x70, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x6c, 0x69, 0x76, 0x65, 0x2f, + 0x00, 0x04, 0x74, 0x79, 0x70, 0x65, + 0x02, 0x00, 0x0a, 0x6e, 0x6f, 0x6e, 0x70, 0x72, 0x69, 0x76, 0x61, 0x74, 0x65, 0x00, + 0x08, 0x66, 0x6c, 0x61, 0x73, 0x68, 0x56, 0x65, 0x72, 0x02, 0x00, 0x1f, 0x46, 0x4d, 0x4c, 0x45, + 0x2f, 0x33, 0x2e, 0x30, 0x20, 0x28, 0x63, 0x6f, 0x6d, 0x70, 0x61, 0x74, 0x69, 0x62, 0x6c, + 0x65, 0x3b, 0x20, 0x46, 0x4d, 0x53, 0x63, 0x2f, 0x31, 0x2e, 0x30, 0x29, 0x00, 0x06, 0x73, 0x77, + 0x66, 0x55, 0x72, 0x6c, + 0x02, 0x00, 0x1A, 0x72, 0x74, 0x6d, 0x70, 0x3a, 0x2f, 0x2f, 0x70, 0x75, 0x73, 0x68, 0x2e, 0x72, 0x74, 0x6d, 0x70, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x6c, 0x69, 0x76, 0x65, 0x2f, + 0x00, 0x04, 0x66, 0x70, 0x61, 0x64, 0x01, 0x00, 0x00, 0x0c, 0x63, 0x61, 0x70, 0x61, 0x62, 0x69, 0x6c, + 0x69, 0x74, 0x69, 0x65, 0x73, 0x00, 0x40, 0x2e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0b, + 0x61, 0x75, 0x64, 0x69, 0x6f, 0x43, 0x6f, 0x64, 0x65, 0x63, 0x73, 0x00, 0x40, 0xa8, 0xee, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x0b, 0x76, 0x69, 0x64, 0x65, 0x6f, 0x43, 0x6f, 0x64, 0x65, + 0x63, 0x73, 0x00, 0x40, 0x6f, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0d, 0x76, 0x69, 0x64, + 0x65, 0x6f, 0x46, 0x75, 0x6e, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x00, 0x3f, 0xf0, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x07, 0x70, 0x61, 0x67, 0x65, 0x55, 0x72, 0x6c, 0x06, 0x00, 0x0e, 0x6f, + 0x62, 0x6a, 0x65, 0x63, 0x74, 0x45, 0x6e, 0x63, 0x6f, 0x64, 0x69, 0x6e, 0x67, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x09, + }; + + char reply[8]; + const uint8_t* end; + double transactionId; + struct rtmp_amf0_connect_t connect; + struct amf_object_item_t commands[11]; + struct amf_object_item_t items[3]; + +#define AMF_OBJECT_ITEM_VALUE(v, amf_type, amf_name, amf_value, amf_size) { v.type=amf_type; v.name=amf_name; v.value=amf_value; v.size=amf_size; } + AMF_OBJECT_ITEM_VALUE(commands[0], AMF_STRING, "app", connect.app, sizeof(connect.app)); + AMF_OBJECT_ITEM_VALUE(commands[1], AMF_STRING, "flashVer", connect.flashver, sizeof(connect.flashver)); + AMF_OBJECT_ITEM_VALUE(commands[2], AMF_STRING, "tcUrl", connect.tcUrl, sizeof(connect.tcUrl)); + AMF_OBJECT_ITEM_VALUE(commands[3], AMF_BOOLEAN, "fpad", &connect.fpad, 1); + AMF_OBJECT_ITEM_VALUE(commands[4], AMF_NUMBER, "audioCodecs", &connect.audioCodecs, 8); + AMF_OBJECT_ITEM_VALUE(commands[5], AMF_NUMBER, "videoCodecs", &connect.videoCodecs, 8); + AMF_OBJECT_ITEM_VALUE(commands[6], AMF_NUMBER, "videoFunction", &connect.videoFunction, 8); + AMF_OBJECT_ITEM_VALUE(commands[7], AMF_NUMBER, "objectEncoding", &connect.encoding, 8); + AMF_OBJECT_ITEM_VALUE(commands[8], AMF_NUMBER, "capabilities", &connect.capabilities, 8); + AMF_OBJECT_ITEM_VALUE(commands[9], AMF_STRING, "pageUrl", &connect.pageUrl, sizeof(connect.pageUrl)); + AMF_OBJECT_ITEM_VALUE(commands[10], AMF_STRING, "swfUrl", &connect.swfUrl, sizeof(connect.swfUrl)); + + AMF_OBJECT_ITEM_VALUE(items[0], AMF_STRING, "reply", reply, sizeof(reply)); // Command object + AMF_OBJECT_ITEM_VALUE(items[1], AMF_NUMBER, "transaction", &transactionId, sizeof(transactionId)); // Command object + AMF_OBJECT_ITEM_VALUE(items[2], AMF_OBJECT, "command", commands, sizeof(commands) / sizeof(commands[0])); // Command object + + end = amf0 + sizeof(amf0); + memset(&connect, 0, sizeof(connect)); + assert(end == amf_read_items(amf0, end, items, sizeof(items) / sizeof(items[0]))); + assert(0 == strcmp(connect.app, "live/")); + assert(0 == strcmp(connect.tcUrl, "rtmp://push.rtmp.com/live/")); + assert(0 == strcmp(connect.flashver, "FMLE/3.0 (compatible; FMSc/1.0)")); + assert(0 == strcmp(connect.swfUrl, "rtmp://push.rtmp.com/live/")); + assert(0 == strcmp(connect.pageUrl, "")); // pageUrl undefined + assert(connect.fpad == 0); + assert(connect.capabilities == 15); + assert(connect.audioCodecs == 3191); + assert(connect.videoCodecs == 252); + assert(connect.videoFunction == 1); + assert(connect.encoding == 0); +} + +void amf0_test(void) +{ + amf0_test_1(); + amf0_test_2(); +} +#endif diff --git a/MediaServer/libflv/source/amf3.c b/MediaServer/libflv/source/amf3.c new file mode 100644 index 0000000..54e005d --- /dev/null +++ b/MediaServer/libflv/source/amf3.c @@ -0,0 +1,95 @@ +#include "amf3.h" +#include +#include + +static double s_double = 1.0; // 3ff0 0000 0000 0000 + +const uint8_t* AMF3ReadNull(const uint8_t* ptr, const uint8_t* end) +{ + (void)end; + return ptr; +} + +const uint8_t* AMF3ReadBoolean(const uint8_t* ptr, const uint8_t* end) +{ + (void)end; + return ptr; +} + +const uint8_t* AMF3ReadInteger(const uint8_t* ptr, const uint8_t* end, int32_t* value) +{ + int i; + int32_t v = 0; + + for (i = 0; i < 3 && ptr + i < end && (0x80 & ptr[i]); i++) + { + v <<= 7; + v |= (ptr[i] & 0x7F); + } + + if (ptr + i >= end) + return NULL; + + if (3 == i) + { + v <<= 8; + v |= ptr[i]; + + if (v >= (1 << 28)) + v -= (1 << 29); + } + else + { + v <<= 7; + v |= ptr[i]; + } + + *value = v; + return ptr + i + 1; +} + +const uint8_t* AMF3ReadDouble(const uint8_t* ptr, const uint8_t* end, double* value) +{ + uint8_t* p = (uint8_t*)value; + if (!ptr || end - ptr < 8) + return NULL; + + if (value) + { + if (0x00 == *(char*)&s_double) + {// Little-Endian + *p++ = ptr[7]; + *p++ = ptr[6]; + *p++ = ptr[5]; + *p++ = ptr[4]; + *p++ = ptr[3]; + *p++ = ptr[2]; + *p++ = ptr[1]; + *p++ = ptr[0]; + } + else + { + memcpy(&value, ptr, 8); + } + } + return ptr + 8; +} + +const uint8_t* AMF3ReadString(const uint8_t* ptr, const uint8_t* end, char* string, uint32_t* length) +{ + uint32_t v; + ptr = AMF3ReadInteger(ptr, end, (int32_t*)&v); + + if (v & 0x01) + { + // reference + return ptr; + } + else + { + *length = v >> 1; + memcpy(string, ptr, *length); + string[*length] = 0; + return ptr + *length; + } +} diff --git a/MediaServer/libflv/source/aom-av1.c b/MediaServer/libflv/source/aom-av1.c new file mode 100644 index 0000000..d9d8309 --- /dev/null +++ b/MediaServer/libflv/source/aom-av1.c @@ -0,0 +1,607 @@ +#include "aom-av1.h" +#include +#include +#include +#include +#include "mpeg4-bits.h" + +// https://aomediacodec.github.io/av1-isobmff +// https://aomediacodec.github.io/av1-avif/ + +enum +{ + OBU_SEQUENCE_HEADER = 1, + OBU_TEMPORAL_DELIMITER = 2, + OBU_FRAME_HEADER = 3, + OBU_TILE_GROUP = 4, + OBU_METADATA = 5, + OBU_FRAME = 6, + OBU_REDUNDANT_FRAME_HEADER = 7, + OBU_TILE_LIST = 8, + // 9-14 Reserved + OBU_PADDING = 15, +}; + +/* +aligned (8) class AV1CodecConfigurationRecord { + unsigned int (1) marker = 1; + unsigned int (7) version = 1; + unsigned int (3) seq_profile; + unsigned int (5) seq_level_idx_0; + unsigned int (1) seq_tier_0; + unsigned int (1) high_bitdepth; + unsigned int (1) twelve_bit; + unsigned int (1) monochrome; + unsigned int (1) chroma_subsampling_x; + unsigned int (1) chroma_subsampling_y; + unsigned int (2) chroma_sample_position; + unsigned int (3) reserved = 0; + + unsigned int (1) initial_presentation_delay_present; + if (initial_presentation_delay_present) { + unsigned int (4) initial_presentation_delay_minus_one; + } else { + unsigned int (4) reserved = 0; + } + + unsigned int (8)[] configOBUs; +} +*/ + +int aom_av1_codec_configuration_record_load(const uint8_t* data, size_t bytes, struct aom_av1_t* av1) +{ + if (bytes < 4) + return -1; + + av1->marker = data[0] >> 7; + av1->version = data[0] & 0x7F; + av1->seq_profile = data[1] >> 5; + av1->seq_level_idx_0 = data[1] & 0x1F; + + av1->seq_tier_0 = data[2] >> 7; + av1->high_bitdepth = (data[2] >> 6) & 0x01; + av1->twelve_bit = (data[2] >> 5) & 0x01; + av1->monochrome = (data[2] >> 4) & 0x01; + av1->chroma_subsampling_x = (data[2] >> 3) & 0x01; + av1->chroma_subsampling_y = (data[2] >> 2) & 0x01; + av1->chroma_sample_position = data[2] & 0x03; + + av1->reserved = data[3] >> 5; + av1->initial_presentation_delay_present = (data[3] >> 4) & 0x01; + av1->initial_presentation_delay_minus_one = data[3] & 0x0F; + + if (bytes - 4 > sizeof(av1->data)) + return -1; + + av1->bytes = (uint16_t)(bytes - 4); + memcpy(av1->data, data + 4, av1->bytes); + return (int)bytes; +} + +int aom_av1_codec_configuration_record_save(const struct aom_av1_t* av1, uint8_t* data, size_t bytes) +{ + if (bytes < (size_t)av1->bytes + 4) + return 0; // don't have enough memory + + data[0] = (uint8_t)((av1->marker << 7) | av1->version); + data[1] = (uint8_t)((av1->seq_profile << 5) | av1->seq_level_idx_0); + data[2] = (uint8_t)((av1->seq_tier_0 << 7) | (av1->high_bitdepth << 6) | (av1->twelve_bit << 5) | (av1->monochrome << 4) | (av1->chroma_subsampling_x << 3) | (av1->chroma_subsampling_y << 2) | av1->chroma_sample_position); + data[3] = (uint8_t)((av1->initial_presentation_delay_present << 4) | av1->initial_presentation_delay_minus_one); + + memcpy(data + 4, av1->data, av1->bytes); + return av1->bytes + 4; +} + +static inline const uint8_t* leb128(const uint8_t* data, int bytes, uint64_t* v) +{ + int i; + uint64_t b; + + b = 0x80; + for (*v = i = 0; i * 7 < 64 && i < bytes && 0 != (b & 0x80); i++) + { + b = data[i]; + *v |= (b & 0x7F) << (i * 7); + } + return data + i; +} + +int aom_av1_annexb_split(const uint8_t* data, size_t bytes, int (*handler)(void* param, const uint8_t* obu, size_t bytes), void* param) +{ + int r; + uint64_t n[3]; + const uint8_t* temporal, * frame, * obu; + + r = 0; + for (temporal = data; temporal < data + bytes && 0 == r; temporal += n[0]) + { + // temporal_unit_size + temporal = leb128(temporal, (int)(data + bytes - temporal), &n[0]); + if (temporal + n[0] > data + bytes) + return -1; + + for (frame = temporal; frame < temporal + n[0] && 0 == r; frame += n[1]) + { + // frame_unit_size + frame = leb128(frame, (int)(temporal + n[0] - frame), &n[1]); + if (frame + n[1] > temporal + n[0]) + return -1; + + for (obu = frame; obu < frame + n[1] && 0 == r; obu += n[2]) + { + obu = leb128(obu, (int)(frame + n[1] - obu), &n[2]); + if (obu + n[2] > frame + n[1]) + return -1; + + r = handler(param, obu, (size_t)n[2]); + } + } + } + + return r; +} + +int aom_av1_obu_split(const uint8_t* data, size_t bytes, int (*handler)(void* param, const uint8_t* obu, size_t bytes), void* param) +{ + int r; + size_t i; + size_t offset; + uint64_t len; + uint8_t obu_type; + const uint8_t* ptr; + + for (i = r = 0; i < bytes && 0 == r; i += (size_t)len) + { + // http://aomedia.org/av1/specification/syntax/#obu-header-syntax + obu_type = (data[i] >> 3) & 0x0F; + if (data[i] & 0x04) // obu_extension_flag + { + // http://aomedia.org/av1/specification/syntax/#obu-extension-header-syntax + // temporal_id = (obu[1] >> 5) & 0x07; + // spatial_id = (obu[1] >> 3) & 0x03; + offset = 2; + } + else + { + offset = 1; + } + + if (data[i] & 0x02) // obu_has_size_field + { + ptr = leb128(data + i + offset, (int)(bytes - i - offset), &len); + if (ptr + len > data + bytes) + return -1; + len += ptr - data - i; + } + else + { + len = bytes - i; + } + + r = handler(param, data + i, (size_t)len); + } + + return r; +} + +// http://aomedia.org/av1/specification/syntax/#color-config-syntax +static int aom_av1_color_config(struct mpeg4_bits_t* bits, struct aom_av1_t* av1) +{ + uint8_t BitDepth; + uint8_t color_primaries; + uint8_t transfer_characteristics; + uint8_t matrix_coefficients; + + av1->high_bitdepth = mpeg4_bits_read(bits); + if (av1->seq_profile == 2 && av1->high_bitdepth) + { + av1->twelve_bit = mpeg4_bits_read(bits); + BitDepth = av1->twelve_bit ? 12 : 10; + } + else if (av1->seq_profile <= 2) + { + BitDepth = av1->high_bitdepth ? 10 : 8; + } + else + { + assert(0); + BitDepth = 8; + } + + if (av1->seq_profile == 1) + { + av1->monochrome = 0; + } + else + { + av1->monochrome = mpeg4_bits_read(bits); + } + + if (mpeg4_bits_read(bits)) // color_description_present_flag + { + color_primaries = mpeg4_bits_read_uint8(bits, 8); // color_primaries + transfer_characteristics = mpeg4_bits_read_uint8(bits, 8); // transfer_characteristics + matrix_coefficients = mpeg4_bits_read_uint8(bits, 8); // matrix_coefficients + } + else + { + // http://aomedia.org/av1/specification/semantics/#color-config-semantics + color_primaries = 2; // CP_UNSPECIFIED; + transfer_characteristics = 2; // TC_UNSPECIFIED; + matrix_coefficients = 2; // MC_UNSPECIFIED; + } + + if (av1->monochrome) + { + mpeg4_bits_read(bits); // color_range + av1->chroma_subsampling_x = 1; + av1->chroma_subsampling_y = 1; + } + else if (color_primaries == 1 /*CP_BT_709*/ && transfer_characteristics == 13 /*TC_SRGB*/ && matrix_coefficients == 0 /*MC_IDENTITY*/) + { + av1->chroma_subsampling_x = 0; + av1->chroma_subsampling_y = 0; + } + else + { + mpeg4_bits_read(bits); // color_range + if (av1->seq_profile == 0) + { + av1->chroma_subsampling_x = 1; + av1->chroma_subsampling_y = 1; + } + else if (av1->seq_profile == 1) + { + av1->chroma_subsampling_x = 0; + av1->chroma_subsampling_y = 0; + } + else + { + if (BitDepth == 12) + { + av1->chroma_subsampling_x = mpeg4_bits_read(bits); + if (av1->chroma_subsampling_x) + av1->chroma_subsampling_y = mpeg4_bits_read(bits); + else + av1->chroma_subsampling_y = 0; + } + else + { + av1->chroma_subsampling_x = 1; + av1->chroma_subsampling_y = 0; + } + } + + if (av1->chroma_subsampling_x && av1->chroma_subsampling_y) + av1->chroma_sample_position = mpeg4_bits_read_uint32(bits, 2); + } + + mpeg4_bits_read(bits); // separate_uv_delta_q + return 0; +} + +// http://aomedia.org/av1/specification/syntax/#timing-info-syntax +static int aom_av1_timing_info(struct mpeg4_bits_t* bits, struct aom_av1_t* av1) +{ + (void)av1; + mpeg4_bits_read_n(bits, 32); // num_units_in_display_tick + mpeg4_bits_read_n(bits, 32); // time_scale + if(mpeg4_bits_read(bits)) // equal_picture_interval + mpeg4_bits_read_uvlc(bits); // num_ticks_per_picture_minus_1 + return 0; +} + +// http://aomedia.org/av1/specification/syntax/#decoder-model-info-syntax +static int aom_av1_decoder_model_info(struct mpeg4_bits_t* bits, struct aom_av1_t* av1) +{ + av1->buffer_delay_length_minus_1 = mpeg4_bits_read_uint8(bits, 5); // buffer_delay_length_minus_1 + mpeg4_bits_read_n(bits, 32); // num_units_in_decoding_tick + mpeg4_bits_read_n(bits, 5); // buffer_removal_time_length_minus_1 + mpeg4_bits_read_n(bits, 5); // frame_presentation_time_length_minus_1 + return 0; +} + +// http://aomedia.org/av1/specification/syntax/#operating-parameters-info-syntax +static int aom_av1_operating_parameters_info(struct mpeg4_bits_t* bits, struct aom_av1_t* av1, int op) +{ + uint8_t n; + n = av1->buffer_delay_length_minus_1 + 1; + mpeg4_bits_read_n(bits, n); // decoder_buffer_delay[ op ] + mpeg4_bits_read_n(bits, n); // encoder_buffer_delay[ op ] + mpeg4_bits_read(bits); // low_delay_mode_flag[ op ] + (void)op; + return 0; +} + +// http://aomedia.org/av1/specification/syntax/#sequence-header-obu-syntax +static int aom_av1_obu_sequence_header(struct aom_av1_t* av1, const void* data, size_t bytes) +{ + uint8_t i; + uint8_t reduced_still_picture_header; + uint8_t decoder_model_info_present_flag; + uint8_t operating_points_cnt_minus_1; + uint8_t frame_width_bits_minus_1; + uint8_t frame_height_bits_minus_1; + uint8_t enable_order_hint; + uint8_t seq_force_screen_content_tools; + struct mpeg4_bits_t bits; + + mpeg4_bits_init(&bits, (void*)data, bytes); + av1->seq_profile = mpeg4_bits_read_uint32(&bits, 3); + mpeg4_bits_read(&bits); // still_picture + reduced_still_picture_header = mpeg4_bits_read_uint8(&bits, 1); + if (reduced_still_picture_header) + { + av1->initial_presentation_delay_present = 0; // initial_display_delay_present_flag + av1->seq_level_idx_0 = mpeg4_bits_read_uint32(&bits, 5); + av1->seq_tier_0 = 0; + decoder_model_info_present_flag = 0; + } + else + { + if (mpeg4_bits_read(&bits)) // timing_info_present_flag + { + // timing_info( ) + aom_av1_timing_info(&bits, av1); + + decoder_model_info_present_flag = mpeg4_bits_read_uint8(&bits, 1); // decoder_model_info_present_flag + if (decoder_model_info_present_flag) + { + // decoder_model_info( ) + aom_av1_decoder_model_info(&bits, av1); + } + } + else + { + decoder_model_info_present_flag = 0; + } + + av1->initial_presentation_delay_present = mpeg4_bits_read(&bits); // initial_display_delay_present_flag = + operating_points_cnt_minus_1 = mpeg4_bits_read_uint8(&bits, 5); + for (i = 0; i <= operating_points_cnt_minus_1; i++) + { + uint8_t seq_level_idx; + uint8_t seq_tier; + uint8_t initial_display_delay_minus_1; + + mpeg4_bits_read_n(&bits, 12); // operating_point_idc[ i ] + seq_level_idx = mpeg4_bits_read_uint8(&bits, 5); // seq_level_idx[ i ] + if (seq_level_idx > 7) + { + seq_tier = mpeg4_bits_read_uint8(&bits, 1); // seq_tier[ i ] + } + else + { + seq_tier = 0; + } + + if (decoder_model_info_present_flag) + { + if (mpeg4_bits_read(&bits)) // decoder_model_present_for_this_op[i] + { + aom_av1_operating_parameters_info(&bits, av1, i); + } + } + + if (av1->initial_presentation_delay_present && mpeg4_bits_read(&bits)) // initial_display_delay_present_for_this_op[ i ] + initial_display_delay_minus_1 = mpeg4_bits_read_uint8(&bits, 4); // initial_display_delay_minus_1[ i ] + else + initial_display_delay_minus_1 = 0; + + if (0 == i) + { + av1->seq_level_idx_0 = seq_level_idx; + av1->seq_tier_0 = seq_tier; + av1->initial_presentation_delay_minus_one = initial_display_delay_minus_1; + } + } + } + + // choose_operating_point( ) + frame_width_bits_minus_1 = mpeg4_bits_read_uint8(&bits, 4); + frame_height_bits_minus_1 = mpeg4_bits_read_uint8(&bits, 4); + av1->width = 1 + mpeg4_bits_read_uint32(&bits, frame_width_bits_minus_1 + 1); // max_frame_width_minus_1 + av1->height = 1 + mpeg4_bits_read_uint32(&bits, frame_height_bits_minus_1 + 1); // max_frame_height_minus_1 + + if (!reduced_still_picture_header && mpeg4_bits_read(&bits)) // frame_id_numbers_present_flag + { + mpeg4_bits_read_n(&bits, 4); // delta_frame_id_length_minus_2 + mpeg4_bits_read_n(&bits, 3); // additional_frame_id_length_minus_1 + } + + mpeg4_bits_read(&bits); // use_128x128_superblock + mpeg4_bits_read(&bits); // enable_filter_intra + mpeg4_bits_read(&bits); // enable_intra_edge_filter + + if (!reduced_still_picture_header) + { + mpeg4_bits_read(&bits); // enable_interintra_compound + mpeg4_bits_read(&bits); // enable_masked_compound + mpeg4_bits_read(&bits); // enable_warped_motion + mpeg4_bits_read(&bits); // enable_dual_filter + enable_order_hint = mpeg4_bits_read_uint8(&bits, 1); + if (enable_order_hint) + { + mpeg4_bits_read(&bits); // enable_jnt_comp + mpeg4_bits_read(&bits); // enable_ref_frame_mvs + } + if (mpeg4_bits_read(&bits)) // seq_choose_screen_content_tools + { + seq_force_screen_content_tools = 2; // SELECT_SCREEN_CONTENT_TOOLS; + } + else + { + seq_force_screen_content_tools = mpeg4_bits_read_uint8(&bits, 1); // seq_force_screen_content_tools + } + + if (seq_force_screen_content_tools > 0) + { + if (!mpeg4_bits_read(&bits)) // seq_choose_integer_mv + mpeg4_bits_read(&bits); // seq_force_integer_mv + //else + // seq_force_integer_mv = SELECT_INTEGER_MV + } + else + { + //seq_force_integer_mv = SELECT_INTEGER_MV; + } + + if (enable_order_hint) + { + mpeg4_bits_read_n(&bits, 3); // order_hint_bits_minus_1 + } + } + + mpeg4_bits_read(&bits); // enable_superres + mpeg4_bits_read(&bits); // enable_cdef + mpeg4_bits_read(&bits); // enable_restoration + + // color_config( ) + aom_av1_color_config(&bits, av1); + + mpeg4_bits_read(&bits); // film_grain_params_present + + return mpeg4_bits_error(&bits) ? -1 : 0; +} + +// http://aomedia.org/av1/specification/syntax/#general-obu-syntax +static int aom_av1_extra_handler(void* param, const uint8_t* obu, size_t bytes) +{ + uint64_t i; + uint64_t len; + size_t offset; + uint8_t obu_type; + const uint8_t* ptr; + struct aom_av1_t* av1; + + av1 = (struct aom_av1_t*)param; + if (bytes < 2) + return -1; + + // http://aomedia.org/av1/specification/syntax/#obu-header-syntax + obu_type = (obu[0] >> 3) & 0x0F; + if (obu[0] & 0x04) // obu_extension_flag + { + // http://aomedia.org/av1/specification/syntax/#obu-extension-header-syntax + // temporal_id = (obu[1] >> 5) & 0x07; + // spatial_id = (obu[1] >> 3) & 0x03; + offset = 2; + } + else + { + offset = 1; + } + + if (obu[0] & 0x02) // obu_has_size_field + { + ptr = leb128(obu + offset, (int)(bytes - offset), &len); + if (ptr + len > obu + bytes) + return -1; + } + else + { + ptr = obu + offset; + len = bytes - offset; + } + + if (OBU_SEQUENCE_HEADER == obu_type || OBU_METADATA == obu_type) + { + if (av1->bytes + bytes + 8 /*leb128*/ >= sizeof(av1->data)) + return -1; + + av1->data[av1->bytes++] = obu[0] | 0x02 /*obu_has_size_field*/; + if (obu[0] & 0x04) // obu_extension_flag + av1->data[av1->bytes++] = obu[1]; + + //if (0 == (obu[0] & 0x02)) + { + // fill obu size, leb128 + for(i = len; i >= 0x80; av1->bytes++) + { + av1->data[av1->bytes] = (uint8_t)(i & 0x7F); + av1->data[av1->bytes] |= 0x80; + i >>= 7; + } + av1->data[av1->bytes++] = (uint8_t)(i & 0x7F); + } + memcpy(av1->data + av1->bytes, ptr, (size_t)len); + av1->bytes += (uint16_t)len; + } + + // http://aomedia.org/av1/specification/semantics/#obu-header-semantics + if (obu_type == OBU_SEQUENCE_HEADER) + { + return aom_av1_obu_sequence_header(av1, ptr, (size_t)len); + } + + return 0; +} + +// https://aomediacodec.github.io/av1-isobmff/#av1codecconfigurationbox-section +int aom_av1_codec_configuration_record_init(struct aom_av1_t* av1, const void* data, size_t bytes) +{ + av1->version = 1; + av1->marker = 1; + return aom_av1_obu_split((const uint8_t*)data, bytes, aom_av1_extra_handler, av1); +} + +int aom_av1_codecs(const struct aom_av1_t* av1, char* codecs, size_t bytes) +{ + unsigned int bitdepth; + + // AV1 5.5.2.Color config syntax + if (2 == av1->seq_profile && av1->high_bitdepth) + bitdepth = av1->twelve_bit ? 12 : 10; + else + bitdepth = av1->high_bitdepth ? 10 : 8; + + // https://aomediacodec.github.io/av1-isobmff/#codecsparam + // https://developer.mozilla.org/en-US/docs/Web/Media/Formats/codecs_parameter + // ......... + return snprintf(codecs, bytes, "av01.%u.%02u%c.%02u", (unsigned int)av1->seq_profile, (unsigned int)av1->seq_level_idx_0, av1->seq_tier_0 ? 'H' : 'M', (unsigned int)bitdepth); +} + +#if defined(_DEBUG) || defined(DEBUG) +void aom_av1_test(void) +{ + const unsigned char src[] = { + 0x81, 0x04, 0x0c, 0x00, 0x0a, 0x0b, 0x00, 0x00, 0x00, 0x24, 0xcf, 0x7f, 0x0d, 0xbf, 0xff, 0x30, 0x08 + }; + unsigned char data[sizeof(src)]; + + struct aom_av1_t av1; + assert(sizeof(src) == aom_av1_codec_configuration_record_load(src, sizeof(src), &av1)); + assert(1 == av1.version && 0 == av1.seq_profile && 4 == av1.seq_level_idx_0); + assert(0 == av1.seq_tier_0 && 0 == av1.high_bitdepth && 0 == av1.twelve_bit && 0 == av1.monochrome && 1 == av1.chroma_subsampling_x && 1 == av1.chroma_subsampling_y && 0 == av1.chroma_sample_position); + assert(0 == av1.initial_presentation_delay_present && 0 == av1.initial_presentation_delay_minus_one); + assert(13 == av1.bytes); + assert(sizeof(src) == aom_av1_codec_configuration_record_save(&av1, data, sizeof(data))); + assert(0 == memcmp(src, data, sizeof(src))); + + aom_av1_codecs(&av1, (char*)data, sizeof(data)); + assert(0 == memcmp("av01.0.04M.08", data, 13)); +} + +void aom_av1_sequence_header_obu_test(void) +{ + const uint8_t obu[] = { /*0x0A, 0x0B,*/ 0x00, 0x00, 0x00, 0x2C, 0xCF, 0x7F, 0x0D, 0xBF, 0xFF, 0x38, 0x18 }; + + struct aom_av1_t av1; + memset(&av1, 0, sizeof(av1)); + assert(0 == aom_av1_obu_sequence_header(&av1, obu, sizeof(obu))); +} + +void aom_av1_obu_test(const char* file) +{ + size_t n; + FILE* fp; + struct aom_av1_t av1; + static uint8_t buffer[24 * 1024 * 1024]; + aom_av1_sequence_header_obu_test(); + memset(&av1, 0, sizeof(av1)); + fp = fopen(file, "rb"); + n = fread(buffer, 1, sizeof(buffer), fp); + aom_av1_codec_configuration_record_init(&av1, buffer, n); + fclose(fp); +} +#endif diff --git a/MediaServer/libflv/source/avswg-avs3.c b/MediaServer/libflv/source/avswg-avs3.c new file mode 100644 index 0000000..949d979 --- /dev/null +++ b/MediaServer/libflv/source/avswg-avs3.c @@ -0,0 +1,90 @@ +#include "avswg-avs3.h" +#include +#include +#include + +#define AVS3_VIDEO_SEQUENCE_START 0xB0 + +/* +aligned(8) class Avs3DecoderConfigurationRecord { + unsigned int(8) configurationVersion = 1; + unsigned int(16) sequence_header_length; + bit(8*sequence_header_length) sequence_header; + bit(6) reserved = '111111'b; + unsigned int(2) library_dependency_idc; +} +*/ + +int avswg_avs3_decoder_configuration_record_load(const uint8_t* data, size_t bytes, struct avswg_avs3_t* avs3) +{ + if (bytes < 4) return -1; + assert(1 == data[0]); + avs3->version = data[0]; + avs3->sequence_header_length = (((uint32_t)data[1]) << 8) | data[2]; + if (avs3->sequence_header_length + 4 > bytes || avs3->sequence_header_length > sizeof(avs3->sequence_header)) + return -1; + + memcpy(avs3->sequence_header, data + 3, avs3->sequence_header_length); + avs3->library_dependency_idc = data[avs3->sequence_header_length + 3] & 0x03; + return avs3->sequence_header_length + 4; +} + +int avswg_avs3_decoder_configuration_record_save(const struct avswg_avs3_t* avs3, uint8_t* data, size_t bytes) +{ + if (bytes < 4 + avs3->sequence_header_length) return -1; + + data[0] = 1; // configurationVersion + data[1] = (uint8_t)(avs3->sequence_header_length >> 8); + data[2] = (uint8_t)(avs3->sequence_header_length); + memcpy(data + 3, avs3->sequence_header, avs3->sequence_header_length); + data[3 + avs3->sequence_header_length] = 0xFC | (uint8_t)(avs3->library_dependency_idc); + return (int)(4 + avs3->sequence_header_length); +} + +int avswg_avs3_codecs(const struct avswg_avs3_t* avs3, char* codecs, size_t bytes) +{ + // // AVS3-P6: Annex-A + return snprintf(codecs, bytes, "avs3.%02x.%02x", (unsigned int)(avs3->sequence_header_length > 6 ? avs3->sequence_header[4] : 0), (unsigned int)(avs3->sequence_header_length > 6 ? avs3->sequence_header[5] : 0)); +} + +int avswg_avs3_decoder_configuration_record_init(struct avswg_avs3_t* avs3, const void* data, size_t bytes) +{ + size_t i; + const uint8_t* p; + + p = data; + if (bytes < 8 || 0 != p[0] || 0 != p[1] || 1 != p[2] || AVS3_VIDEO_SEQUENCE_START != p[3]) + return -1; + + for (i = 0; i + 1 < sizeof(avs3->sequence_header) + && (i < 4 || i + 3 >= bytes || 0 != p[i] || 0 != p[i + 1] || 1 != p[i + 2]) + ; i++) + { + avs3->sequence_header[i] = p[i]; + } + + avs3->version = 1; + avs3->sequence_header_length = (uint32_t)i; + avs3->library_dependency_idc = 0; + return 0; +} + +#if defined(_DEBUG) || defined(DEBUG) +void avswg_avs3_test(void) +{ + const unsigned char src[] = { + 0x01,0x00,0x1d,0x00,0x00,0x01,0xb0,0x20,0x44,0x88,0xf0,0x11,0x0e,0x13,0x16,0x87,0x2b, + 0x10,0x00,0x20,0x10,0xcf,0xcf,0xc1,0x06,0x14,0x10,0x10,0x67,0x0f,0x04,0x48,0xfc, + }; + unsigned char data[sizeof(src)]; + + struct avswg_avs3_t avs3; + assert(sizeof(src) == avswg_avs3_decoder_configuration_record_load(src, sizeof(src), &avs3)); + assert(1 == avs3.version && 0x1d == avs3.sequence_header_length && 0 == avs3.library_dependency_idc); + assert(sizeof(src) == avswg_avs3_decoder_configuration_record_save(&avs3, data, sizeof(data))); + assert(0 == memcmp(src, data, sizeof(src))); + + avswg_avs3_codecs(&avs3, (char*)data, sizeof(data)); + assert(0 == memcmp("avs3.20.44", data, 10)); +} +#endif diff --git a/MediaServer/libflv/source/flv-demuxer-script.c b/MediaServer/libflv/source/flv-demuxer-script.c new file mode 100644 index 0000000..9158355 --- /dev/null +++ b/MediaServer/libflv/source/flv-demuxer-script.c @@ -0,0 +1,84 @@ +#include "flv-demuxer.h" +#include "amf0.h" +#include +#include +#include + +#define N_ONMETADATA 12 // 2-LEN + 10-onMetaData + +/// http://www.cnblogs.com/musicfans/archive/2012/11/07/2819291.html +/// metadata keyframes/filepositions +/// @return >0-OK, 0-don't metadata, <0-error +int flv_demuxer_script(struct flv_demuxer_t* flv, const uint8_t* data, size_t bytes) +{ + const uint8_t* end; + char buffer[64] = { 0 }; + double audiocodecid = 0; + double audiodatarate = 0; // bitrate / 1024 + double audiodelay = 0; + double audiosamplerate = 0; + double audiosamplesize = 0; + double videocodecid = 0; + double videodatarate = 0; // bitrate / 1024 + double framerate = 0; + double height = 0; + double width = 0; + double duration = 0; + double filesize = 0; + int canSeekToEnd = 0; + int stereo = 0; + struct amf_object_item_t keyframes[2]; + struct amf_object_item_t prop[16]; + struct amf_object_item_t items[1]; + +#define AMF_OBJECT_ITEM_VALUE(v, amf_type, amf_name, amf_value, amf_size) { v.type=amf_type; v.name=amf_name; v.value=amf_value; v.size=amf_size; } + AMF_OBJECT_ITEM_VALUE(keyframes[0], AMF_STRICT_ARRAY, "filepositions", NULL, 0); // ignore keyframes + AMF_OBJECT_ITEM_VALUE(keyframes[1], AMF_STRICT_ARRAY, "times", NULL, 0); + + AMF_OBJECT_ITEM_VALUE(prop[0], AMF_NUMBER, "audiocodecid", &audiocodecid, sizeof(audiocodecid)); + AMF_OBJECT_ITEM_VALUE(prop[1], AMF_NUMBER, "audiodatarate", &audiodatarate, sizeof(audiodatarate)); + AMF_OBJECT_ITEM_VALUE(prop[2], AMF_NUMBER, "audiodelay", &audiodelay, sizeof(audiodelay)); + AMF_OBJECT_ITEM_VALUE(prop[3], AMF_NUMBER, "audiosamplerate", &audiosamplerate, sizeof(audiosamplerate)); + AMF_OBJECT_ITEM_VALUE(prop[4], AMF_NUMBER, "audiosamplesize", &audiosamplesize, sizeof(audiosamplesize)); + AMF_OBJECT_ITEM_VALUE(prop[5], AMF_BOOLEAN, "stereo", &stereo, sizeof(stereo)); + + AMF_OBJECT_ITEM_VALUE(prop[6], AMF_BOOLEAN, "canSeekToEnd", &canSeekToEnd, sizeof(canSeekToEnd)); + AMF_OBJECT_ITEM_VALUE(prop[7], AMF_STRING, "creationdate", buffer, sizeof(buffer)); + AMF_OBJECT_ITEM_VALUE(prop[8], AMF_NUMBER, "duration", &duration, sizeof(duration)); + AMF_OBJECT_ITEM_VALUE(prop[9], AMF_NUMBER, "filesize", &filesize, sizeof(filesize)); + + AMF_OBJECT_ITEM_VALUE(prop[10], AMF_NUMBER, "videocodecid", &videocodecid, sizeof(videocodecid)); + AMF_OBJECT_ITEM_VALUE(prop[11], AMF_NUMBER, "videodatarate", &videodatarate, sizeof(videodatarate)); + AMF_OBJECT_ITEM_VALUE(prop[12], AMF_NUMBER, "framerate", &framerate, sizeof(framerate)); + AMF_OBJECT_ITEM_VALUE(prop[13], AMF_NUMBER, "height", &height, sizeof(height)); + AMF_OBJECT_ITEM_VALUE(prop[14], AMF_NUMBER, "width", &width, sizeof(width)); + + AMF_OBJECT_ITEM_VALUE(prop[15], AMF_OBJECT, "keyframes", keyframes, 2); // FLV I-index + + AMF_OBJECT_ITEM_VALUE(items[0], AMF_OBJECT, "onMetaData", prop, sizeof(prop) / sizeof(prop[0])); +#undef AMF_OBJECT_ITEM_VALUE + + end = data + bytes; + if (AMF_STRING != data[0] || NULL == (data = AMFReadString(data + 1, end, 0, buffer, sizeof(buffer) - 1))) + { + assert(0); + return -1; + } + + // filter @setDataFrame + if (0 == strcmp(buffer, "@setDataFrame")) + { + if (AMF_STRING != data[0] || NULL == (data = AMFReadString(data + 1, end, 0, buffer, sizeof(buffer) - 1))) + { + assert(0); + return -1; + } + } + + // onTextData/onCaption/onCaptionInfo/onCuePoint/|RtmpSampleAccess + if (0 != strcmp(buffer, "onMetaData")) + return 0; // skip + + (void)flv; + return amf_read_items(data, end, items, sizeof(items) / sizeof(items[0])) ? N_ONMETADATA : -1; +} diff --git a/MediaServer/libflv/source/flv-demuxer.c b/MediaServer/libflv/source/flv-demuxer.c new file mode 100644 index 0000000..5f4eed7 --- /dev/null +++ b/MediaServer/libflv/source/flv-demuxer.c @@ -0,0 +1,347 @@ +#include "flv-demuxer.h" +#include "flv-header.h" +#include "flv-proto.h" +#include "mpeg4-aac.h" +#include "mpeg4-avc.h" +#include "mpeg4-hevc.h" +#include "mpeg4-vvc.h" +#include "opus-head.h" +#include "aom-av1.h" +#include "avswg-avs3.h" +#include "amf0.h" +#include +#include +#include +#include + +struct flv_demuxer_t +{ + union + { + struct mpeg4_aac_t aac; + struct opus_head_t opus; + } a; + + union + { + struct aom_av1_t av1; + struct mpeg4_avc_t avc; + struct mpeg4_hevc_t hevc; + struct mpeg4_vvc_t vvc; + struct avswg_avs3_t avs3; + } v; + + flv_demuxer_handler handler; + void* param; + + uint8_t* ptr; + int capacity; +}; + +struct flv_demuxer_t* flv_demuxer_create(flv_demuxer_handler handler, void* param) +{ + struct flv_demuxer_t* flv; + flv = (struct flv_demuxer_t*)malloc(sizeof(struct flv_demuxer_t)); + if (NULL == flv) + return NULL; + + memset(flv, 0, sizeof(struct flv_demuxer_t)); + flv->handler = handler; + flv->param = param; + return flv; +} + +void flv_demuxer_destroy(struct flv_demuxer_t* flv) +{ + if (flv->ptr) + { + assert(flv->capacity > 0); + free(flv->ptr); + } + + free(flv); +} + +static int flv_demuxer_check_and_alloc(struct flv_demuxer_t* flv, int bytes) +{ + if (bytes > flv->capacity) + { + void* p = realloc(flv->ptr, bytes); + if (NULL == p) + return -1; + flv->ptr = (uint8_t*)p; + flv->capacity = bytes; + } + return 0; +} + +static int flv_demuxer_audio(struct flv_demuxer_t* flv, const uint8_t* data, int bytes, uint32_t timestamp) +{ + int r, n; + struct flv_audio_tag_header_t audio; + n = flv_audio_tag_header_read(&audio, data, bytes); + if (n < 0) + return n; + + if (FLV_AUDIO_AAC == audio.codecid) + { + // Adobe Flash Video File Format Specification Version 10.1 >> E.4.2.1 AUDIODATA (p77) + // If the SoundFormat indicates AAC, the SoundType should be 1 (stereo) and the SoundRate should be 3 (44 kHz). + // However, this does not mean that AAC audio in FLV is always stereo, 44 kHz data.Instead, the Flash Player ignores + // these values and extracts the channel and sample rate data is encoded in the AAC bit stream. + //assert(3 == audio.bitrate && 1 == audio.channel); + if (FLV_SEQUENCE_HEADER == audio.avpacket) + { + flv->a.aac.profile = MPEG4_AAC_LC; + flv->a.aac.sampling_frequency_index = MPEG4_AAC_44100; + flv->a.aac.channel_configuration = 2; + flv->a.aac.channels = 2; + flv->a.aac.sampling_frequency = 44100; + flv->a.aac.extension_frequency = 44100; + mpeg4_aac_audio_specific_config_load(data + n, bytes - n, &flv->a.aac); + return flv->handler(flv->param, FLV_AUDIO_ASC, data + n, bytes - n, timestamp, timestamp, 0); + } + else + { + if (0 != flv_demuxer_check_and_alloc(flv, bytes + 7 + 1 + flv->a.aac.npce)) + return -ENOMEM; + + // AAC ES stream with ADTS header + assert(bytes <= 0x1FFF); + assert(bytes > 2 && 0xFFF0 != (((data[2] << 8) | data[3]) & 0xFFF0)); // don't have ADTS + r = mpeg4_aac_adts_save(&flv->a.aac, (uint16_t)bytes - n, flv->ptr, 7 + 1 + flv->a.aac.npce); // 13-bits + if (r < 7) return -EINVAL; // invalid pce + flv->a.aac.npce = 0; // pce write only once + memmove(flv->ptr + r, data + n, bytes - n); + return flv->handler(flv->param, FLV_AUDIO_AAC, flv->ptr, bytes - n + r, timestamp, timestamp, 0); + } + } + else if (FLV_AUDIO_OPUS == audio.codecid) + { + if (FLV_SEQUENCE_HEADER == audio.avpacket) + { + opus_head_load(data + n, bytes - n, &flv->a.opus); + return flv->handler(flv->param, FLV_AUDIO_OPUS_HEAD, data + n, bytes - n, timestamp, timestamp, 0); + } + else + { + return flv->handler(flv->param, audio.codecid, data + n, bytes - n, timestamp, timestamp, 0); + } + } + else if (FLV_AUDIO_MP3 == audio.codecid || FLV_AUDIO_MP3_8K == audio.codecid) + { + return flv->handler(flv->param, audio.codecid, data + n, bytes - n, timestamp, timestamp, 0); + } + else + { + // Audio frame data + return flv->handler(flv->param, audio.codecid, data + n, bytes - n, timestamp, timestamp, 0); + } +} + +static int flv_demuxer_video(struct flv_demuxer_t* flv, const uint8_t* data, int bytes, uint32_t timestamp) +{ + int n; + struct flv_video_tag_header_t video; + n = flv_video_tag_header_read(&video, data, bytes); + if (n < 0) + return n; + + if (FLV_VIDEO_H264 == video.codecid) + { + if (FLV_SEQUENCE_HEADER == video.avpacket) + { + // AVCDecoderConfigurationRecord + assert(bytes > n + 7); + mpeg4_avc_decoder_configuration_record_load(data + n, bytes - n, &flv->v.avc); + return flv->handler(flv->param, FLV_VIDEO_AVCC, data + n, bytes - n, timestamp + video.cts, timestamp, 0); + } + else if(FLV_AVPACKET == video.avpacket) + { + // feat: h264_mp4toannexb support flv->v.avc.nalu == 0 + //assert(flv->v.avc.nalu > 0); // parse AVCDecoderConfigurationRecord failed + //if (flv->v.avc.nalu > 0 && bytes > n) // 5 == bytes flv eof + { + // H.264 + if (0 != flv_demuxer_check_and_alloc(flv, bytes + 4 * 1024)) + return -ENOMEM; + + assert(flv->v.avc.nalu <= 4); + n = h264_mp4toannexb(&flv->v.avc, data + n, bytes - n, flv->ptr, flv->capacity); + if (n <= 0 || n > flv->capacity) + { + assert(0); + return -ENOMEM; + } + return flv->handler(flv->param, FLV_VIDEO_H264, flv->ptr, n, timestamp + video.cts, timestamp, (FLV_VIDEO_KEY_FRAME == video.keyframe) ? 1 : 0); + } + return -EINVAL; + } + else if (FLV_END_OF_SEQUENCE == video.avpacket) + { + return 0; // AVC end of sequence (lower level NALU sequence ender is not required or supported) + } + else + { + assert(0); + return -EINVAL; + } + } + else if (FLV_VIDEO_H265 == video.codecid) + { + if (FLV_SEQUENCE_HEADER == video.avpacket) + { + // HEVCDecoderConfigurationRecord + assert(bytes > n + 7); + mpeg4_hevc_decoder_configuration_record_load(data + n, bytes - n, &flv->v.hevc); + return flv->handler(flv->param, FLV_VIDEO_HVCC, data + n, bytes - n, timestamp + video.cts, timestamp, 0); + } + else if (FLV_AVPACKET == video.avpacket) + { + // feat: h265_mp4toannexb support flv->v.hevc.numOfArrays == 0 + //assert(flv->v.hevc.numOfArrays > 0); // parse HEVCDecoderConfigurationRecord failed + //if (flv->v.hevc.numOfArrays > 0 && bytes > n) // 5 == bytes flv eof + { + // H.265 + if (0 != flv_demuxer_check_and_alloc(flv, bytes + 4 * 1024)) + return -ENOMEM; + + n = h265_mp4toannexb(&flv->v.hevc, data + n, bytes - n, flv->ptr, flv->capacity); + if (n <= 0 || n > flv->capacity) + { + assert(0); + return -ENOMEM; + } + return flv->handler(flv->param, FLV_VIDEO_H265, flv->ptr, n, timestamp + video.cts, timestamp, (FLV_VIDEO_KEY_FRAME == video.keyframe) ? 1 : 0); + } + return -EINVAL; + } + else if (FLV_END_OF_SEQUENCE == video.avpacket) + { + return 0; // AVC end of sequence (lower level NALU sequence ender is not required or supported) + } + else + { + assert(0); + return -EINVAL; + } + } + else if (FLV_VIDEO_H266 == video.codecid) + { + if (FLV_SEQUENCE_HEADER == video.avpacket) + { + // VVCDecoderConfigurationRecord + assert(bytes > n + 5); + mpeg4_vvc_decoder_configuration_record_load(data + n, bytes - n, &flv->v.vvc); + return flv->handler(flv->param, FLV_VIDEO_VVCC, data + n, bytes - n, timestamp + video.cts, timestamp, 0); + } + else if (FLV_AVPACKET == video.avpacket) + { + // feat: h266_mp4toannexb support flv->v.vvc.numOfArrays == 0 + //assert(flv->v.vvc.numOfArrays > 0); // parse VVCDecoderConfigurationRecord failed + //if (flv->v.vvc.numOfArrays > 0 && bytes > n) // 5 == bytes flv eof + { + // H.266 + if (0 != flv_demuxer_check_and_alloc(flv, bytes + 4 * 1024)) + return -ENOMEM; + + n = h266_mp4toannexb(&flv->v.vvc, data + n, bytes - n, flv->ptr, flv->capacity); + if (n <= 0 || n > flv->capacity) + { + assert(0); + return -ENOMEM; + } + return flv->handler(flv->param, FLV_VIDEO_H266, flv->ptr, n, timestamp + video.cts, timestamp, (FLV_VIDEO_KEY_FRAME == video.keyframe) ? 1 : 0); + } + return -EINVAL; + } + else if (FLV_END_OF_SEQUENCE == video.avpacket) + { + return 0; // AVC end of sequence (lower level NALU sequence ender is not required or supported) + } + else + { + assert(0); + return -EINVAL; + } + } + else if (FLV_VIDEO_AV1 == video.codecid) + { + if (FLV_SEQUENCE_HEADER == video.avpacket) + { + // AV1CodecConfigurationRecord + assert(bytes > n + 5); + aom_av1_codec_configuration_record_load(data + n, bytes - n, &flv->v.av1); + return flv->handler(flv->param, FLV_VIDEO_AV1C, data + n, bytes - n, timestamp + video.cts, timestamp, 0); + } + else if (FLV_AVPACKET == video.avpacket) + { + return flv->handler(flv->param, FLV_VIDEO_AV1, data + n, bytes - n, timestamp + video.cts, timestamp, (FLV_VIDEO_KEY_FRAME == video.keyframe) ? 1 : 0); + } + else if (FLV_END_OF_SEQUENCE == video.avpacket) + { + return 0; // AV1 end of sequence (lower level NALU sequence ender is not required or supported) + } + else + { + assert(0); + return -EINVAL; + } + } + else if (FLV_VIDEO_AVS3 == video.codecid) + { + if (FLV_SEQUENCE_HEADER == video.avpacket) + { + // AVS3DecoderConfigurationRecord + assert(bytes > n + 5); + avswg_avs3_decoder_configuration_record_load(data + n, bytes - n, &flv->v.avs3); + return flv->handler(flv->param, FLV_VIDEO_AVSC, data + n, bytes - n, timestamp + video.cts, timestamp, 0); + } + else if (FLV_AVPACKET == video.avpacket) + { + return flv->handler(flv->param, FLV_VIDEO_AVS3, data + n, bytes - n, timestamp + video.cts, timestamp, (FLV_VIDEO_KEY_FRAME == video.keyframe) ? 1 : 0); + } + else if (FLV_END_OF_SEQUENCE == video.avpacket) + { + return 0; // AVC end of sequence (lower level NALU sequence ender is not required or supported) + } + else + { + assert(0); + return -EINVAL; + } + } + else + { + // Video frame data + return flv->handler(flv->param, video.codecid, data + n, bytes - n, timestamp + video.cts, timestamp, (FLV_VIDEO_KEY_FRAME==video.keyframe) ? 1 : 0); + } +} + +int flv_demuxer_script(struct flv_demuxer_t* flv, const uint8_t* data, size_t bytes); +int flv_demuxer_input(struct flv_demuxer_t* flv, int type, const void* data, size_t bytes, uint32_t timestamp) +{ + int n; + if (bytes < 1) + return 0; + + switch (type) + { + case FLV_TYPE_AUDIO: + return flv_demuxer_audio(flv, data, (int)bytes, timestamp); + + case FLV_TYPE_VIDEO: + return flv_demuxer_video(flv, data, (int)bytes, timestamp); + + case FLV_TYPE_SCRIPT: + n = flv_demuxer_script(flv, data, bytes); + if (n < 12) + return 0; // ignore + n -= 12; // 2-LEN + 10-onMetaData + return flv->handler(flv->param, FLV_SCRIPT_METADATA, (const uint8_t*)data + n, bytes - n, timestamp, timestamp, 0); + + default: + assert(0); + return -1; + } +} diff --git a/MediaServer/libflv/source/flv-header.c b/MediaServer/libflv/source/flv-header.c new file mode 100644 index 0000000..7376034 --- /dev/null +++ b/MediaServer/libflv/source/flv-header.c @@ -0,0 +1,333 @@ +#include "flv-header.h" +#include "flv-proto.h" +#include +#include + +#define N_TAG_SIZE 4 // previous tag size +#define FLV_HEADER_SIZE 9 // DataOffset included +#define FLV_TAG_HEADER_SIZE 11 // StreamID included + +static inline uint32_t be_read_uint32(const uint8_t* ptr) +{ + return (ptr[0] << 24) | (ptr[1] << 16) | (ptr[2] << 8) | ptr[3]; +} + +static inline void be_write_uint32(uint8_t* ptr, uint32_t val) +{ + ptr[0] = (uint8_t)((val >> 24) & 0xFF); + ptr[1] = (uint8_t)((val >> 16) & 0xFF); + ptr[2] = (uint8_t)((val >> 8) & 0xFF); + ptr[3] = (uint8_t)(val & 0xFF); +} + +int flv_header_read(struct flv_header_t* flv, const uint8_t* buf, size_t len) +{ + if (len < FLV_HEADER_SIZE || 'F' != buf[0] || 'L' != buf[1] || 'V' != buf[2]) + { + assert(0); + return -1; + } + + flv->FLV[0] = buf[0]; + flv->FLV[1] = buf[1]; + flv->FLV[2] = buf[2]; + flv->version = buf[3]; + + assert(0x00 == (buf[4] & 0xF8) && 0x00 == (buf[4] & 0x20)); + flv->audio = (buf[4] >> 2) & 0x01; + flv->video = buf[4] & 0x01; + flv->offset = be_read_uint32(buf + 5); + + return FLV_HEADER_SIZE; +} + +int flv_tag_header_read(struct flv_tag_header_t* tag, const uint8_t* buf, size_t len) +{ + if (len < FLV_TAG_HEADER_SIZE) + { + assert(0); + return -1; + } + + // TagType + tag->type = buf[0] & 0x1F; + tag->filter = (buf[0] >> 5) & 0x01; + assert(FLV_TYPE_VIDEO == tag->type || FLV_TYPE_AUDIO == tag->type || FLV_TYPE_SCRIPT == tag->type); + + // DataSize + tag->size = ((uint32_t)buf[1] << 16) | ((uint32_t)buf[2] << 8) | buf[3]; + + // TimestampExtended | Timestamp + tag->timestamp = ((uint32_t)buf[4] << 16) | ((uint32_t)buf[5] << 8) | buf[6] | ((uint32_t)buf[7] << 24); + + // StreamID Always 0 + tag->streamId = ((uint32_t)buf[8] << 16) | ((uint32_t)buf[9] << 8) | buf[10]; + //assert(0 == tag->streamId); + + return FLV_TAG_HEADER_SIZE; +} + +int flv_audio_tag_header_read(struct flv_audio_tag_header_t* audio, const uint8_t* buf, size_t len) +{ + assert(len > 0); + audio->codecid = (buf[0] & 0xF0) /*>> 4*/; + audio->rate = (buf[0] & 0x0C) >> 2; + audio->bits = (buf[0] & 0x02) >> 1; + audio->channels = buf[0] & 0x01; + audio->avpacket = FLV_AVPACKET; + + if (FLV_AUDIO_AAC == audio->codecid || FLV_AUDIO_OPUS == audio->codecid) + { + if (len < 2) + { + assert(0); + return -1; + } + audio->avpacket = buf[1]; + assert(FLV_SEQUENCE_HEADER == audio->avpacket || FLV_AVPACKET == audio->avpacket); + return 2; + } + else + { + return 1; + } +} + +int flv_video_tag_header_read(struct flv_video_tag_header_t* video, const uint8_t* buf, size_t len) +{ + assert(len > 0); + if (len >= 5 && 0 != (buf[0] & 0x80)) + { + // https://github.com/veovera/enhanced-rtmp/blob/main/enhanced-rtmp.pdf + + video->keyframe = (buf[0] & 0x70) >> 4; + video->avpacket = (buf[0] & 0x0F); + video->cts = 0; // default + switch (FLV_VIDEO_FOURCC(buf[1], buf[2], buf[3], buf[4])) + { + case FLV_VIDEO_FOURCC_AV1: + video->codecid = FLV_VIDEO_AV1; + return 5; + + //case FLV_VIDEO_FOURCC_VP9: + // video->codecid = FLV_VIDEO_VP9; + // break; + + case FLV_VIDEO_FOURCC_HEVC: + case FLV_VIDEO_FOURCC_VVC: + video->codecid = (FLV_VIDEO_FOURCC(buf[1], buf[2], buf[3], buf[4]) == FLV_VIDEO_FOURCC_HEVC) ? FLV_VIDEO_H265 : FLV_VIDEO_H266; + if(len >= 8 && FLV_AVPACKET == video->avpacket) + { + video->cts = ((uint32_t)buf[5] << 16) | ((uint32_t)buf[6] << 8) | buf[7]; + //if (video->cts >= (1 << 23)) video->cts -= (1 << 24); + video->cts = (video->cts + 0xFF800000) ^ 0xFF800000; // signed 24-integer + return 8; + } + else + { + if (FLV_PACKET_TYPE_CODED_FRAMES_X == video->avpacket) + video->avpacket = FLV_AVPACKET; + video->cts = 0; + return 5; + } + break; + + default: + video->codecid = 0; // unknown + } + + return 5; + } + + video->keyframe = (buf[0] & 0xF0) >> 4; + video->codecid = (buf[0] & 0x0F); + video->avpacket = FLV_AVPACKET; + + if (FLV_VIDEO_H264 == video->codecid || FLV_VIDEO_H265 == video->codecid || FLV_VIDEO_H266 == video->codecid || FLV_VIDEO_AV1 == video->codecid || FLV_VIDEO_AVS3 == video->codecid) + { + if (len < 5) + return -1; + + video->avpacket = buf[1]; // AVCPacketType + video->cts = ((uint32_t)buf[2] << 16) | ((uint32_t)buf[3] << 8) | buf[4]; + //if (video->cts >= (1 << 23)) video->cts -= (1 << 24); + video->cts = (video->cts + 0xFF800000) ^ 0xFF800000; // signed 24-integer + assert(FLV_SEQUENCE_HEADER == video->avpacket || FLV_AVPACKET == video->avpacket || FLV_END_OF_SEQUENCE == video->avpacket); + return 5; + } + else + { + return 1; + } +} + +int flv_data_tag_header_read(const uint8_t* buf, size_t len) +{ + (void)buf; + return (int)len; +} + +int flv_header_write(int audio, int video, uint8_t* buf, size_t len) +{ + if (len < FLV_HEADER_SIZE) + { + assert(0); + return -1; + } + + buf[0] = 'F'; // FLV signature + buf[1] = 'L'; + buf[2] = 'V'; + buf[3] = 0x01; // File version + buf[4] = ((audio ? 1 : 0) << 2) | (video ? 1 : 0); // Type flags (audio & video) + be_write_uint32(buf + 5, FLV_HEADER_SIZE); // Data offset + return FLV_HEADER_SIZE; +} + +int flv_tag_header_write(const struct flv_tag_header_t* tag, uint8_t* buf, size_t len) +{ + if (len < FLV_TAG_HEADER_SIZE) + { + assert(0); + return -1; + } + + // TagType + assert(FLV_TYPE_VIDEO == tag->type || FLV_TYPE_AUDIO == tag->type || FLV_TYPE_SCRIPT == tag->type); + buf[0] = (tag->type & 0x1F) | ((tag->filter & 0x01) << 5); + + // DataSize + buf[1] = (tag->size >> 16) & 0xFF; + buf[2] = (tag->size >> 8) & 0xFF; + buf[3] = tag->size & 0xFF; + + // Timestamp + buf[4] = (tag->timestamp >> 16) & 0xFF; + buf[5] = (tag->timestamp >> 8) & 0xFF; + buf[6] = (tag->timestamp >> 0) & 0xFF; + buf[7] = (tag->timestamp >> 24) & 0xFF; // Timestamp Extended + + // StreamID(Always 0) + buf[8] = (tag->streamId >> 16) & 0xFF; + buf[9] = (tag->streamId >> 8) & 0xFF; + buf[10] = (tag->streamId) & 0xFF; + + return FLV_TAG_HEADER_SIZE; +} + +int flv_audio_tag_header_write(const struct flv_audio_tag_header_t* audio, uint8_t* buf, size_t len) +{ + if ((int)len < 1 + ((FLV_AUDIO_AAC == audio->codecid || FLV_AUDIO_OPUS == audio->codecid)? 1 : 0)) + return -1; + + if (FLV_AUDIO_AAC == audio->codecid || FLV_AUDIO_OPUS == audio->codecid) + { + assert(FLV_SEQUENCE_HEADER == audio->avpacket || FLV_AVPACKET == audio->avpacket); + buf[0] = (audio->codecid /* <<4 */) /* SoundFormat */ | (3 << 2) /* 44k-SoundRate */ | (1 << 1) /* 16-bit samples */ | 1 /* Stereo sound */; + buf[1] = audio->avpacket; // AACPacketType + return 2; + } + else + { + buf[0] = (audio->codecid /* <<4 */) | ((audio->rate & 0x03) << 2) | ((audio->bits & 0x01) << 1) | (audio->channels & 0x01); + return 1; + } +} + +int flv_video_tag_header_write(const struct flv_video_tag_header_t* video, uint8_t* buf, size_t len) +{ +#ifdef FLV_ENHANCE_RTMP + // https://github.com/veovera/enhanced-rtmp/blob/main/enhanced-rtmp.pdf + + if (len < 5) + return -1; + + buf[0] = 0x80 | (video->keyframe << 4) /*FrameType*/; + buf[0] |= (0 == video->cts && FLV_AVPACKET == video->avpacket) ? FLV_PACKET_TYPE_CODED_FRAMES_X : video->avpacket; + + switch (video->codecid) + { + case FLV_VIDEO_AV1: + buf[1] = (FLV_VIDEO_FOURCC_AV1 >> 24) & 0xFF; + buf[2] = (FLV_VIDEO_FOURCC_AV1 >> 16) & 0xFF; + buf[3] = (FLV_VIDEO_FOURCC_AV1 >> 8) & 0xFF; + buf[4] = (FLV_VIDEO_FOURCC_AV1) & 0xFF; + return 5; + + case FLV_VIDEO_H265: + buf[1] = (FLV_VIDEO_FOURCC_HEVC >> 24) & 0xFF; + buf[2] = (FLV_VIDEO_FOURCC_HEVC >> 16) & 0xFF; + buf[3] = (FLV_VIDEO_FOURCC_HEVC >> 8) & 0xFF; + buf[4] = (FLV_VIDEO_FOURCC_HEVC) & 0xFF; + if (len >= 8 && FLV_AVPACKET == video->avpacket && video->cts != 0) + { + buf[5] = (video->cts >> 16) & 0xFF; + buf[6] = (video->cts >> 8) & 0xFF; + buf[7] = video->cts & 0xFF; + return 8; + } + return 5; + + case FLV_VIDEO_H266: + buf[1] = (FLV_VIDEO_FOURCC_VVC >> 24) & 0xFF; + buf[2] = (FLV_VIDEO_FOURCC_VVC >> 16) & 0xFF; + buf[3] = (FLV_VIDEO_FOURCC_VVC >> 8) & 0xFF; + buf[4] = (FLV_VIDEO_FOURCC_VVC) & 0xFF; + if (len >= 8 && FLV_AVPACKET == video->avpacket && video->cts != 0) + { + buf[5] = (video->cts >> 16) & 0xFF; + buf[6] = (video->cts >> 8) & 0xFF; + buf[7] = video->cts & 0xFF; + return 8; + } + return 5; + + default: + break; // fallthrough + } + +#endif + + if (len < 1) + return -1; + + buf[0] = (video->keyframe << 4) /*FrameType*/ | (video->codecid & 0x0F) /*CodecID*/; + + if (FLV_VIDEO_H264 == video->codecid || FLV_VIDEO_H265 == video->codecid || FLV_VIDEO_H266 == video->codecid || FLV_VIDEO_AV1 == video->codecid) + { + assert(FLV_SEQUENCE_HEADER == video->avpacket || FLV_AVPACKET == video->avpacket || FLV_END_OF_SEQUENCE == video->avpacket); + if (len < 5) + return -1; + + buf[1] = video->avpacket; // AVCPacketType + buf[2] = (video->cts >> 16) & 0xFF; + buf[3] = (video->cts >> 8) & 0xFF; + buf[4] = video->cts & 0xFF; + return 5; + } + + return 1; +} + +int flv_data_tag_header_write(uint8_t* buf, size_t len) +{ + (void)buf; + (void)len; + return 0; +} + +int flv_tag_size_read(const uint8_t* buf, size_t len, uint32_t* size) +{ + if(len < 4) + return -1; + *size = be_read_uint32(buf); + return 4; +} + +int flv_tag_size_write(uint8_t* buf, size_t len, uint32_t size) +{ + if(len < 4) + return -1; + be_write_uint32(buf, size); + return 4; +} diff --git a/MediaServer/libflv/source/flv-muxer.c b/MediaServer/libflv/source/flv-muxer.c new file mode 100644 index 0000000..6597fab --- /dev/null +++ b/MediaServer/libflv/source/flv-muxer.c @@ -0,0 +1,567 @@ +#include "flv-muxer.h" +#include "flv-proto.h" +#include "flv-header.h" +#include "amf0.h" +#include +#include +#include +#include +#include +#include +#include "aom-av1.h" +#include "mpeg4-aac.h" +#include "mpeg4-avc.h" +#include "mpeg4-vvc.h" +#include "mpeg4-hevc.h" +#include "avswg-avs3.h" +#include "mp3-header.h" +#include "opus-head.h" + +#define FLV_MUXER "ireader/media-server" + +struct flv_muxer_t +{ + flv_muxer_handler handler; + void* param; + + uint8_t audio_sequence_header; + uint8_t video_sequence_header; + + union + { + struct mpeg4_aac_t aac; + struct opus_head_t opus; + } a; + + union + { + struct aom_av1_t av1; + struct mpeg4_avc_t avc; + struct mpeg4_hevc_t hevc; + struct mpeg4_vvc_t vvc; + struct avswg_avs3_t avs3; + } v; + int vcl; // 0-non vcl, 1-idr, 2-p/b + int update; // avc/hevc sequence header update + + uint8_t* ptr; + size_t bytes; + size_t capacity; +}; + +struct flv_muxer_t* flv_muxer_create(flv_muxer_handler handler, void* param) +{ + struct flv_muxer_t* flv; + flv = (struct flv_muxer_t*)calloc(1, sizeof(struct flv_muxer_t)); + if (NULL == flv) + return NULL; + + flv_muxer_reset(flv); + flv->handler = handler; + flv->param = param; + return flv; +} + +void flv_muxer_destroy(struct flv_muxer_t* flv) +{ + if (flv->ptr) + { + assert(flv->capacity > 0); + free(flv->ptr); + flv->ptr = NULL; + } + + free(flv); +} + +int flv_muxer_reset(struct flv_muxer_t* flv) +{ + memset(&flv->v, 0, sizeof(flv->v)); + flv->audio_sequence_header = 0; + flv->video_sequence_header = 0; + return 0; +} + +static int flv_muxer_alloc(struct flv_muxer_t* flv, size_t bytes) +{ + void* p; + p = realloc(flv->ptr, bytes); + if (!p) + return -ENOMEM; + + flv->ptr = (uint8_t*)p; + flv->capacity = bytes; + return 0; +} + +int flv_muxer_g711a(struct flv_muxer_t* flv, const void* data, size_t bytes, uint32_t pts, uint32_t dts) +{ + struct flv_audio_tag_header_t audio; + (void)pts; + + if (flv->capacity < bytes + 1) + { + if (0 != flv_muxer_alloc(flv, bytes + 4)) + return -ENOMEM; + } + + audio.bits = FLV_SOUND_BIT_16; // 16-bit samples + audio.channels = FLV_SOUND_CHANNEL_MONO; + audio.rate = 0; + audio.codecid = FLV_AUDIO_G711A; + audio.avpacket = FLV_AVPACKET; + flv_audio_tag_header_write(&audio, flv->ptr, 1); + memcpy(flv->ptr + 1, data, bytes); + return flv->handler(flv->param, FLV_TYPE_AUDIO, flv->ptr, bytes + 1, dts); +} + +int flv_muxer_g711u(struct flv_muxer_t* flv, const void* data, size_t bytes, uint32_t pts, uint32_t dts) +{ + struct flv_audio_tag_header_t audio; + (void)pts; + + if (flv->capacity < bytes + 1) + { + if (0 != flv_muxer_alloc(flv, bytes + 4)) + return -ENOMEM; + } + + audio.bits = FLV_SOUND_BIT_16; // 16-bit samples + audio.channels = FLV_SOUND_CHANNEL_MONO; + audio.rate = 0; + audio.codecid = FLV_AUDIO_G711U; + audio.avpacket = FLV_AVPACKET; + flv_audio_tag_header_write(&audio, flv->ptr, 1); + memcpy(flv->ptr + 1, data, bytes); + return flv->handler(flv->param, FLV_TYPE_AUDIO, flv->ptr, bytes + 1, dts); +} + +int flv_muxer_mp3(struct flv_muxer_t* flv, const void* data, size_t bytes, uint32_t pts, uint32_t dts) +{ + struct mp3_header_t mp3; + struct flv_audio_tag_header_t audio; + (void)pts; + + if (0 == mp3_header_load(&mp3, data, (int)bytes)) + { + return -EINVAL; + } + else + { + audio.channels = 3 == mp3.mode ? FLV_SOUND_CHANNEL_MONO : FLV_SOUND_CHANNEL_STEREO; + switch (mp3_get_frequency(&mp3)) + { + case 5500: audio.rate = FLV_SOUND_RATE_5500; break; + case 11025: audio.rate = FLV_SOUND_RATE_11025; break; + case 22050: audio.rate = FLV_SOUND_RATE_22050; break; + case 44100: audio.rate = FLV_SOUND_RATE_44100; break; + default: audio.rate = FLV_SOUND_RATE_44100; + } + } + + if (flv->capacity < bytes + 1) + { + if (0 != flv_muxer_alloc(flv, bytes + 4)) + return -ENOMEM; + } + + audio.bits = FLV_SOUND_BIT_16; // 16-bit samples + audio.codecid = FLV_AUDIO_MP3; + audio.avpacket = FLV_AVPACKET; + flv_audio_tag_header_write(&audio, flv->ptr, 1); + memcpy(flv->ptr + 1, data, bytes); // MP3 + return flv->handler(flv->param, FLV_TYPE_AUDIO, flv->ptr, bytes + 1, dts); +} + +int flv_muxer_aac(struct flv_muxer_t* flv, const void* data, size_t bytes, uint32_t pts, uint32_t dts) +{ + int r, n, m; + struct flv_audio_tag_header_t audio; + (void)pts; + + if (flv->capacity < bytes + 2/*AudioTagHeader*/ + 2/*AudioSpecificConfig*/) + { + if (0 != flv_muxer_alloc(flv, bytes + 4)) + return -ENOMEM; + } + + /* ADTS */ + n = mpeg4_aac_adts_load(data, bytes, &flv->a.aac); + if (n <= 0) + return -1; // invalid data + + audio.codecid = FLV_AUDIO_AAC; + audio.rate = FLV_SOUND_RATE_44100; // 44k-SoundRate + audio.bits = FLV_SOUND_BIT_16; // 16-bit samples + audio.channels = FLV_SOUND_CHANNEL_STEREO; // Stereo sound + if (0 == flv->audio_sequence_header) + { + flv->audio_sequence_header = 1; // once only + audio.avpacket = FLV_SEQUENCE_HEADER; + + // AudioSpecificConfig(AAC sequence header) + flv_audio_tag_header_write(&audio, flv->ptr, flv->capacity); + m = mpeg4_aac_audio_specific_config_save(&flv->a.aac, flv->ptr + 2, flv->capacity - 2); + assert(m + 2 <= (int)flv->capacity); + r = flv->handler(flv->param, FLV_TYPE_AUDIO, flv->ptr, m + 2, dts); + if (0 != r) return r; + } + + audio.avpacket = FLV_AVPACKET; + flv_audio_tag_header_write(&audio, flv->ptr, flv->capacity); + memcpy(flv->ptr + 2, (uint8_t*)data + n, bytes - n); // AAC exclude ADTS + assert(bytes - n + 2 <= flv->capacity); + return flv->handler(flv->param, FLV_TYPE_AUDIO, flv->ptr, bytes - n + 2, dts); +} + +int flv_muxer_opus(flv_muxer_t* flv, const void* data, size_t bytes, uint32_t pts, uint32_t dts) +{ + int r, m; + struct flv_audio_tag_header_t audio; + (void)pts; + + if (flv->capacity < bytes + 2/*AudioTagHeader*/ + 29/*OpusHead*/) + { + if (0 != flv_muxer_alloc(flv, bytes + 4)) + return -ENOMEM; + } + + audio.codecid = FLV_AUDIO_OPUS; + audio.rate = FLV_SOUND_RATE_44100; // 44k-SoundRate + audio.bits = FLV_SOUND_BIT_16; // 16-bit samples + audio.channels = FLV_SOUND_CHANNEL_STEREO; // Stereo sound + + if (0 == flv->audio_sequence_header) + { + if (opus_head_load(data, bytes, &flv->a.opus) < 0) + return -1; + + flv->audio_sequence_header = 1; // once only + audio.avpacket = FLV_SEQUENCE_HEADER; + + // Opus Head + m = flv_audio_tag_header_write(&audio, flv->ptr, flv->capacity); + m += opus_head_save(&flv->a.opus, flv->ptr+m, flv->capacity-m); + assert(m <= (int)flv->capacity); + r = flv->handler(flv->param, FLV_TYPE_AUDIO, flv->ptr, m, dts); + if (0 != r) return r; + } + + audio.avpacket = FLV_AVPACKET; + m = flv_audio_tag_header_write(&audio, flv->ptr, flv->capacity); + memcpy(flv->ptr + m, (uint8_t*)data, bytes); + assert(bytes - m <= flv->capacity); + return flv->handler(flv->param, FLV_TYPE_AUDIO, flv->ptr, bytes + m, dts); +} + +static int flv_muxer_h264(struct flv_muxer_t* flv, uint32_t pts, uint32_t dts) +{ + int r; + int m; + struct flv_video_tag_header_t video; + + video.codecid = FLV_VIDEO_H264; + if ( /*0 == flv->video_sequence_header &&*/ flv->update && flv->v.avc.nb_sps > 0 && flv->v.avc.nb_pps > 0) + { + video.cts = 0; + video.keyframe = 1; // keyframe + video.avpacket = FLV_SEQUENCE_HEADER; + flv_video_tag_header_write(&video, flv->ptr + flv->bytes, flv->capacity - flv->bytes); + m = mpeg4_avc_decoder_configuration_record_save(&flv->v.avc, flv->ptr + flv->bytes + 5, flv->capacity - flv->bytes - 5); + if (m <= 0) + return -1; // invalid data + + flv->video_sequence_header = 1; // once only + assert(flv->bytes + m + 5 <= flv->capacity); + r = flv->handler(flv->param, FLV_TYPE_VIDEO, flv->ptr + flv->bytes, m + 5, dts); + if (0 != r) return r; + } + + // has video frame + if (flv->vcl && flv->video_sequence_header) + { + video.cts = pts - dts; + video.keyframe = 1 == flv->vcl ? FLV_VIDEO_KEY_FRAME : FLV_VIDEO_INTER_FRAME; + video.avpacket = FLV_AVPACKET; + flv_video_tag_header_write(&video, flv->ptr, flv->capacity); + assert(flv->bytes <= flv->capacity); + return flv->handler(flv->param, FLV_TYPE_VIDEO, flv->ptr, flv->bytes, dts); + } + return 0; +} + +int flv_muxer_avc(struct flv_muxer_t* flv, const void* data, size_t bytes, uint32_t pts, uint32_t dts) +{ + if (flv->capacity < bytes + sizeof(flv->v.avc) /*AVCDecoderConfigurationRecord*/) + { + if (0 != flv_muxer_alloc(flv, bytes + sizeof(flv->v.avc))) + return -ENOMEM; + } + + flv->bytes = 5; + flv->bytes += h264_annexbtomp4(&flv->v.avc, data, bytes, flv->ptr + flv->bytes, flv->capacity - flv->bytes, &flv->vcl, &flv->update); + if (flv->bytes <= 5) + return -ENOMEM; + + return flv_muxer_h264(flv, pts, dts); +} + +static int flv_muxer_h265(struct flv_muxer_t* flv, uint32_t pts, uint32_t dts) +{ + int r; + int m, n; + struct flv_video_tag_header_t video; + + video.codecid = FLV_VIDEO_H265; + if ( /*0 == flv->avc_sequence_header &&*/ flv->update && flv->v.hevc.numOfArrays >= 3) // vps + sps + pps + { + video.cts = 0; + video.keyframe = 1; // keyframe + video.avpacket = FLV_SEQUENCE_HEADER; + n = flv_video_tag_header_write(&video, flv->ptr + flv->bytes, flv->capacity - flv->bytes); + m = mpeg4_hevc_decoder_configuration_record_save(&flv->v.hevc, flv->ptr + flv->bytes + n, flv->capacity - flv->bytes - n); + if (m <= 0) + return -1; // invalid data + + flv->video_sequence_header = 1; // once only + assert(flv->bytes + m + n <= flv->capacity); + r = flv->handler(flv->param, FLV_TYPE_VIDEO, flv->ptr + flv->bytes, m + n, dts); + if (0 != r) return r; + } + + // has video frame + if (flv->vcl && flv->video_sequence_header) + { + video.cts = pts - dts; + video.keyframe = 1 == flv->vcl ? FLV_VIDEO_KEY_FRAME : FLV_VIDEO_INTER_FRAME; + video.avpacket = FLV_AVPACKET; + n = flv_video_tag_header_write(&video, flv->ptr, flv->capacity); + assert(flv->bytes <= flv->capacity); + return flv->handler(flv->param, FLV_TYPE_VIDEO, flv->ptr, flv->bytes, dts); + } + return 0; +} + +int flv_muxer_hevc(struct flv_muxer_t* flv, const void* data, size_t bytes, uint32_t pts, uint32_t dts) +{ + if ((size_t)flv->capacity < bytes + sizeof(flv->v.hevc) /*HEVCDecoderConfigurationRecord*/) + { + if (0 != flv_muxer_alloc(flv, bytes + sizeof(flv->v.hevc))) + return -ENOMEM; + } + + flv->bytes = 5; +#ifdef FLV_ENHANCE_RTMP + flv->bytes += dts == pts ? 0 : 3; +#endif + flv->bytes += h265_annexbtomp4(&flv->v.hevc, data, bytes, flv->ptr + flv->bytes, flv->capacity - flv->bytes, &flv->vcl, &flv->update); + if (flv->bytes <= 5) + return -ENOMEM; + + return flv_muxer_h265(flv, pts, dts); +} + +static int flv_muxer_h266(struct flv_muxer_t* flv, uint32_t pts, uint32_t dts) +{ + int r; + int m, n; + struct flv_video_tag_header_t video; + + video.codecid = FLV_VIDEO_H266; + if ( /*0 == flv->avc_sequence_header &&*/ flv->update && flv->v.vvc.numOfArrays >= 3) // vps + sps + pps + { + video.cts = 0; + video.keyframe = 1; // keyframe + video.avpacket = FLV_SEQUENCE_HEADER; + n = flv_video_tag_header_write(&video, flv->ptr + flv->bytes, flv->capacity - flv->bytes); + m = mpeg4_vvc_decoder_configuration_record_save(&flv->v.vvc, flv->ptr + flv->bytes + n, flv->capacity - flv->bytes - n); + if (m <= 0) + return -1; // invalid data + + flv->video_sequence_header = 1; // once only + assert(flv->bytes + m + n <= flv->capacity); + r = flv->handler(flv->param, FLV_TYPE_VIDEO, flv->ptr + flv->bytes, m + n, dts); + if (0 != r) return r; + } + + // has video frame + if (flv->vcl && flv->video_sequence_header) + { + video.cts = pts - dts; + video.keyframe = 1 == flv->vcl ? FLV_VIDEO_KEY_FRAME : FLV_VIDEO_INTER_FRAME; + video.avpacket = FLV_AVPACKET; + n = flv_video_tag_header_write(&video, flv->ptr, flv->capacity); + assert(flv->bytes <= flv->capacity); + return flv->handler(flv->param, FLV_TYPE_VIDEO, flv->ptr, flv->bytes, dts); + } + return 0; +} + +int flv_muxer_vvc(struct flv_muxer_t* flv, const void* data, size_t bytes, uint32_t pts, uint32_t dts) +{ + if ((size_t)flv->capacity < bytes + sizeof(flv->v.vvc) /*HEVCDecoderConfigurationRecord*/) + { + if (0 != flv_muxer_alloc(flv, bytes + sizeof(flv->v.vvc))) + return -ENOMEM; + } + + flv->bytes = 5; +#ifdef FLV_ENHANCE_RTMP + flv->bytes += dts == pts ? 0 : 3; +#endif + flv->bytes += h266_annexbtomp4(&flv->v.vvc, data, bytes, flv->ptr + flv->bytes, flv->capacity - flv->bytes, &flv->vcl, &flv->update); + if (flv->bytes <= 5) + return -ENOMEM; + + return flv_muxer_h266(flv, pts, dts); +} + +int flv_muxer_av1(flv_muxer_t* flv, const void* data, size_t bytes, uint32_t pts, uint32_t dts) +{ + int r; + int m; + struct flv_video_tag_header_t video; + + if ((size_t)flv->capacity < bytes + 5 + sizeof(flv->v.av1) /*HEVCDecoderConfigurationRecord*/) + { + if (0 != flv_muxer_alloc(flv, bytes + sizeof(flv->v.av1))) + return -ENOMEM; + } + + video.codecid = FLV_VIDEO_AV1; + if (0 == flv->video_sequence_header) + { + // load av1 information + r = aom_av1_codec_configuration_record_init(&flv->v.av1, data, bytes); + if (0 != r || flv->v.av1.width < 1 || flv->v.av1.height < 1) + return 0 == r ? -1 : r; + + video.cts = 0; + video.keyframe = 1; // keyframe + video.avpacket = FLV_SEQUENCE_HEADER; + flv_video_tag_header_write(&video, flv->ptr + flv->bytes, flv->capacity - flv->bytes); + m = aom_av1_codec_configuration_record_save(&flv->v.av1, flv->ptr + flv->bytes + 5, flv->capacity - flv->bytes - 5); + if (m <= 0) + return -1; // invalid data + + flv->video_sequence_header = 1; // once only + assert(flv->bytes + m + 5 <= flv->capacity); + r = flv->handler(flv->param, FLV_TYPE_VIDEO, flv->ptr + flv->bytes, m + 5, dts); + if (0 != r) return r; + } + + // has video frame + if (flv->video_sequence_header) + { + video.cts = pts - dts; + video.keyframe = 1 == flv->vcl ? FLV_VIDEO_KEY_FRAME : FLV_VIDEO_INTER_FRAME; + video.avpacket = FLV_AVPACKET; + flv_video_tag_header_write(&video, flv->ptr, flv->capacity); + memcpy(flv->ptr + 5, data, bytes); + return flv->handler(flv->param, FLV_TYPE_VIDEO, flv->ptr, bytes + 5, dts); + } + return 0; +} + +int flv_muxer_avs3(flv_muxer_t* flv, const void* data, size_t bytes, uint32_t pts, uint32_t dts) +{ + int r; + int m; + struct flv_video_tag_header_t video; + + if ((size_t)flv->capacity < bytes + 5 + sizeof(flv->v.avs3) /*AVS3DecoderConfigurationRecord*/) + { + if (0 != flv_muxer_alloc(flv, bytes + sizeof(flv->v.avs3))) + return -ENOMEM; + } + + video.codecid = FLV_VIDEO_H266; // codec 14, same as H.266 + if (0 == flv->video_sequence_header) + { + // load avs information + r = avswg_avs3_decoder_configuration_record_init(&flv->v.avs3, data, bytes); + if (0 != r || flv->v.avs3.sequence_header_length < 1) + return 0 == r ? -1 : r; + + video.cts = 0; + video.keyframe = 1; // keyframe + video.avpacket = FLV_SEQUENCE_HEADER; + flv_video_tag_header_write(&video, flv->ptr + flv->bytes, flv->capacity - flv->bytes); + m = avswg_avs3_decoder_configuration_record_save(&flv->v.avs3, flv->ptr + flv->bytes + 5, flv->capacity - flv->bytes - 5); + if (m <= 0) + return -1; // invalid data + + flv->video_sequence_header = 1; // once only + assert(flv->bytes + m + 5 <= flv->capacity); + r = flv->handler(flv->param, FLV_TYPE_VIDEO, flv->ptr + flv->bytes, m + 5, dts); + if (0 != r) return r; + } + + // has video frame + if (flv->video_sequence_header) + { + video.cts = pts - dts; + video.keyframe = 1 == flv->vcl ? FLV_VIDEO_KEY_FRAME : FLV_VIDEO_INTER_FRAME; + video.avpacket = FLV_AVPACKET; + flv_video_tag_header_write(&video, flv->ptr, flv->capacity); + memcpy(flv->ptr + 5, data, bytes); + return flv->handler(flv->param, FLV_TYPE_VIDEO, flv->ptr, bytes + 5, dts); + } + return 0; +} + +int flv_muxer_metadata(flv_muxer_t* flv, const struct flv_metadata_t* metadata) +{ + uint8_t* ptr, *end; + uint32_t count; + + if (!metadata) return -1; + + if (flv->capacity < 1024) + { + if (0 != flv_muxer_alloc(flv, 1024)) + return -ENOMEM; + } + + ptr = flv->ptr; + end = flv->ptr + flv->capacity; + count = (metadata->audiocodecid ? 5 : 0) + (metadata->videocodecid ? 7 : 0) + 1; + + // ScriptTagBody + + // name + ptr = AMFWriteString(ptr, end, "onMetaData", 10); + + // value: SCRIPTDATAECMAARRAY + ptr[0] = AMF_ECMA_ARRAY; + ptr[1] = (uint8_t)((count >> 24) & 0xFF);; + ptr[2] = (uint8_t)((count >> 16) & 0xFF);; + ptr[3] = (uint8_t)((count >> 8) & 0xFF); + ptr[4] = (uint8_t)(count & 0xFF); + ptr += 5; + + if (metadata->audiocodecid) + { + ptr = AMFWriteNamedDouble(ptr, end, "audiocodecid", 12, metadata->audiocodecid); + ptr = AMFWriteNamedDouble(ptr, end, "audiodatarate", 13, metadata->audiodatarate /* / 1024.0*/); + ptr = AMFWriteNamedDouble(ptr, end, "audiosamplerate", 15, metadata->audiosamplerate); + ptr = AMFWriteNamedDouble(ptr, end, "audiosamplesize", 15, metadata->audiosamplesize); + ptr = AMFWriteNamedBoolean(ptr, end, "stereo", 6, (uint8_t)metadata->stereo); + } + + if (metadata->videocodecid) + { + ptr = AMFWriteNamedDouble(ptr, end, "duration", 8, metadata->duration); + ptr = AMFWriteNamedDouble(ptr, end, "interval", 8, metadata->interval); + ptr = AMFWriteNamedDouble(ptr, end, "videocodecid", 12, metadata->videocodecid); + ptr = AMFWriteNamedDouble(ptr, end, "videodatarate", 13, metadata->videodatarate /* / 1024.0*/); + ptr = AMFWriteNamedDouble(ptr, end, "framerate", 9, metadata->framerate); + ptr = AMFWriteNamedDouble(ptr, end, "height", 6, metadata->height); + ptr = AMFWriteNamedDouble(ptr, end, "width", 5, metadata->width); + } + + ptr = AMFWriteNamedString(ptr, end, "encoder", 7, FLV_MUXER, strlen(FLV_MUXER)); + ptr = AMFWriteObjectEnd(ptr, end); + + return flv->handler(flv->param, FLV_TYPE_SCRIPT, flv->ptr, ptr - flv->ptr, 0); +} diff --git a/MediaServer/libflv/source/flv-parser.c b/MediaServer/libflv/source/flv-parser.c new file mode 100644 index 0000000..4cb78bf --- /dev/null +++ b/MediaServer/libflv/source/flv-parser.c @@ -0,0 +1,261 @@ +#include "flv-parser.h" +#include "flv-header.h" +#include "flv-proto.h" +#include +#include +#include +#include + +#define N_TAG_SIZE 4 // previous tag size +#define FLV_HEADER_SIZE 9 // DataOffset included +#define FLV_TAG_HEADER_SIZE 11 // StreamID included + +#define FLV_VIDEO_CODEC_NAME(codecid) (FLV_VIDEO_H264==(codecid) ? FLV_VIDEO_AVCC : (FLV_VIDEO_H265==(codecid) ? FLV_VIDEO_HVCC : (FLV_VIDEO_H266==(codecid) ? FLV_VIDEO_VVCC : FLV_VIDEO_AV1C))) + +static int flv_parser_audio(struct flv_audio_tag_header_t* audio, const uint8_t* data, size_t bytes, uint32_t timestamp, flv_parser_handler handler, void* param) +{ + if (FLV_SEQUENCE_HEADER == audio->avpacket) + return handler(param, FLV_AUDIO_AAC == audio->codecid ? FLV_AUDIO_ASC : FLV_AUDIO_OPUS_HEAD, data, bytes, timestamp, timestamp, 0); + else + return handler(param, audio->codecid, data, bytes, timestamp, timestamp, 0); +} + +static int flv_parser_video(struct flv_video_tag_header_t* video, const uint8_t* data, size_t bytes, uint32_t timestamp, flv_parser_handler handler, void* param) +{ + if (FLV_VIDEO_H264 == video->codecid || FLV_VIDEO_H265 == video->codecid || FLV_VIDEO_H266 == video->codecid || FLV_VIDEO_AV1 == video->codecid) + { + if (FLV_SEQUENCE_HEADER == video->avpacket) + { + return handler(param, FLV_VIDEO_CODEC_NAME(video->codecid), data, bytes, timestamp, timestamp, 0); + } + else if (FLV_AVPACKET == video->avpacket) + { + return handler(param, video->codecid, data, bytes, timestamp + video->cts, timestamp, (FLV_VIDEO_KEY_FRAME == video->keyframe) ? 1 : 0); + } + else if (FLV_END_OF_SEQUENCE == video->avpacket) + { + return 0; // AVC end of sequence (lower level NALU sequence ender is not required or supported) + } + else + { + assert(0); + return -EINVAL; + } + } + else + { + // Video frame data + return handler(param, video->codecid, data, bytes, timestamp, timestamp, (FLV_VIDEO_KEY_FRAME == video->keyframe) ? 1 : 0); + } +} + +// http://www.cnblogs.com/musicfans/archive/2012/11/07/2819291.html +// metadata keyframes/filepositions +static int flv_parser_script(const uint8_t* data, size_t bytes, uint32_t timestamp, flv_parser_handler handler, void* param) +{ + return handler(param, FLV_SCRIPT_METADATA, data, bytes, timestamp, timestamp, 0); +} + +int flv_parser_tag(int type, const void* data, size_t bytes, uint32_t timestamp, flv_parser_handler handler, void* param) +{ + int n; + struct flv_audio_tag_header_t audio; + struct flv_video_tag_header_t video; + + if (bytes < 1) return -EINVAL; + + switch (type) + { + case FLV_TYPE_AUDIO: + n = flv_audio_tag_header_read(&audio, data, bytes); + if (n < 0) + return n; + return flv_parser_audio(&audio, (const uint8_t*)data + n, (int)bytes - n, timestamp, handler, param); + + case FLV_TYPE_VIDEO: + n = flv_video_tag_header_read(&video, data, bytes); + if (n < 0) + return n; + return flv_parser_video(&video, (const uint8_t*)data + n, (int)bytes - n, timestamp, handler, param); + + case FLV_TYPE_SCRIPT: + n = flv_data_tag_header_read(data, bytes); + if (n < 0) + return n; + return flv_parser_script((const uint8_t*)data + n, (int)bytes - n, timestamp, handler, param); + + default: + assert(0); + return -1; + } +} + +static size_t flv_parser_append(struct flv_parser_t* parser, const uint8_t* data, size_t bytes, size_t expect) +{ + size_t n; + if (parser->bytes > expect || expect > sizeof(parser->ptr)) + { + // invalid status, consume all + assert(0); + parser->bytes = expect; + return bytes; + } + + n = parser->bytes + bytes >= expect ? expect - parser->bytes : bytes; + if (n > 0) + { + memcpy(parser->ptr + parser->bytes, data, n); + parser->bytes += n; + } + return n; +} + +int flv_parser_input(struct flv_parser_t* parser, const uint8_t* data, size_t bytes, flv_parser_handler handler, void* param) +{ + int r; + size_t n; + uint8_t codec; + uint32_t size; + enum {FLV_HEADER=0, FLV_HEADER_OFFSET, FLV_PREVIOUS_SIZE, FLV_TAG_HEADER, FLV_AVHEADER_CODEC, FLV_AVHEADER_EXTRA, FLV_TAG_BODY}; + + for (n = r = 0; bytes > 0 && n >= 0 && 0 == r; data += n, bytes -= n) + { + switch (parser->state) + { + case FLV_HEADER: + n = flv_parser_append(parser, data, bytes, FLV_HEADER_SIZE); + if (FLV_HEADER_SIZE == parser->bytes) + { + flv_header_read(&parser->header, parser->ptr, parser->bytes); + if (parser->header.offset < 9 || parser->header.offset > sizeof(parser->ptr)) + return -1; + parser->header.offset -= 9; + parser->state = parser->header.offset > 0 ? FLV_HEADER_OFFSET : FLV_PREVIOUS_SIZE; + parser->bytes = 0; + } + break; + + case FLV_HEADER_OFFSET: + n = flv_parser_append(parser, data, bytes, parser->header.offset); + if (parser->header.offset == (uint32_t)parser->bytes) + { + parser->bytes = 0; + parser->state = FLV_PREVIOUS_SIZE; + } + break; + + case FLV_PREVIOUS_SIZE: + n = flv_parser_append(parser, data, bytes, N_TAG_SIZE); + if (N_TAG_SIZE == parser->bytes) + { + flv_tag_size_read(parser->ptr, parser->bytes, &size); + assert(size == 0 || size == parser->tag.size + FLV_TAG_HEADER_SIZE); + parser->bytes = 0; + parser->state = FLV_TAG_HEADER; + } + break; + + case FLV_TAG_HEADER: + n = flv_parser_append(parser, data, bytes, FLV_TAG_HEADER_SIZE); + if (FLV_TAG_HEADER_SIZE == parser->bytes) + { + flv_tag_header_read(&parser->tag, parser->ptr, parser->bytes); + parser->bytes = 0; + parser->expect = 0; + parser->state = FLV_AVHEADER_CODEC; + } + break; + + case FLV_AVHEADER_CODEC: + switch (parser->tag.type) + { + case FLV_TYPE_AUDIO: + parser->expect = 1; + n = flv_parser_append(parser, data, bytes, 1); + codec = (parser->ptr[0] & 0xF0) /*>> 4*/; + if (FLV_AUDIO_AAC == codec || FLV_AUDIO_OPUS == codec) + parser->expect = 2; + break; + + case FLV_TYPE_VIDEO: + parser->expect = 1; + n = flv_parser_append(parser, data, bytes, 1); + codec = (parser->ptr[0] & 0x0F); + if (FLV_VIDEO_H264 == codec || FLV_VIDEO_H265 == codec || FLV_VIDEO_H266 == codec || FLV_VIDEO_AV1 == codec) + parser->expect = 5; + break; + + case FLV_TYPE_SCRIPT: + parser->expect = 0; + n = 0; // noops + break; + + default: + assert(0); + return -1; // invalid flv file + } + parser->state = FLV_AVHEADER_EXTRA; + break; + + case FLV_AVHEADER_EXTRA: + n = flv_parser_append(parser, data, bytes, parser->expect); + if (parser->expect == parser->bytes) + { + if(FLV_TYPE_AUDIO == parser->tag.type) + flv_audio_tag_header_read(&parser->audio, parser->ptr, parser->bytes); + else if(FLV_TYPE_VIDEO == parser->tag.type) + flv_video_tag_header_read(&parser->video, parser->ptr, parser->bytes); + parser->bytes = 0; + parser->state = FLV_TAG_BODY; + + parser->expect = parser->tag.size - parser->expect; + parser->body = parser->alloc ? parser->alloc(param, parser->expect) : malloc(parser->expect); + if (!parser->body) + return -1; + } + break; + + case FLV_TAG_BODY: + assert(parser->body && parser->bytes <= parser->expect); + n = parser->bytes + bytes >= parser->expect ? parser->expect - parser->bytes : bytes; + if(n > 0) { + memmove(parser->body + parser->bytes, data, n); + parser->bytes += n; + } + + if (parser->expect == parser->bytes) + { + parser->bytes = 0; + parser->state = FLV_PREVIOUS_SIZE; + switch (parser->tag.type) + { + case FLV_TYPE_AUDIO: + r = flv_parser_audio(&parser->audio, parser->body, parser->expect, parser->tag.timestamp, handler, param); + break; + + case FLV_TYPE_VIDEO: + r = flv_parser_video(&parser->video, parser->body, parser->expect, parser->tag.timestamp, handler, param); + break; + + case FLV_TYPE_SCRIPT: + r = flv_parser_script(parser->body, parser->expect, parser->tag.timestamp, handler, param); + break; + + default: + assert(0); + r = -1; + break; + } + + parser->free ? parser->free(param, parser->body) : free(parser->body); + } + break; + + default: + assert(0); + return -1; + } + } + + return r; +} diff --git a/MediaServer/libflv/source/flv-reader.c b/MediaServer/libflv/source/flv-reader.c new file mode 100644 index 0000000..bdf0d82 --- /dev/null +++ b/MediaServer/libflv/source/flv-reader.c @@ -0,0 +1,135 @@ +#include "flv-reader.h" +#include "flv-header.h" +#include "flv-proto.h" +#include +#include +#include +#include + +#define FLV_HEADER_SIZE 9 // DataOffset included +#define FLV_TAG_HEADER_SIZE 11 // StreamID included + +struct flv_reader_t +{ + FILE* fp; + int (*read)(void* param, void* buf, int len); + void* param; +}; + +static int flv_read_header(struct flv_reader_t* flv) +{ + uint32_t sz; + uint8_t data[FLV_HEADER_SIZE]; + struct flv_header_t h; + int n; + + if (FLV_HEADER_SIZE != flv->read(flv->param, data, FLV_HEADER_SIZE)) + return -1; + + if(FLV_HEADER_SIZE != flv_header_read(&h, data, FLV_HEADER_SIZE)) + return -1; + + assert(h.offset >= FLV_HEADER_SIZE && h.offset < FLV_HEADER_SIZE + 4096); + for(n = (int)(h.offset - FLV_HEADER_SIZE); n > 0 && n < 4096; n -= sizeof(data)) + flv->read(flv->param, data, n >= sizeof(data) ? sizeof(data) : n); // skip + + // PreviousTagSize0 + if (4 != flv->read(flv->param, data, 4)) + return -1; + + flv_tag_size_read(data, 4, &sz); + assert(0 == sz); + return 0 == sz ? 0 : -1; +} + +static int file_read(void* param, void* buf, int len) +{ + return (int)fread(buf, 1, len, (FILE*)param); +} + +void* flv_reader_create(const char* file) +{ + FILE* fp; + struct flv_reader_t* flv; + fp = fopen(file, "rb"); + if (!fp) + return NULL; + + flv = flv_reader_create2(file_read, fp); + if (!flv) + { + fclose(fp); + return NULL; + } + + flv->fp = fp; + return flv; +} + +void* flv_reader_create2(int (*read)(void* param, void* buf, int len), void* param) +{ + struct flv_reader_t* flv; + flv = (struct flv_reader_t*)calloc(1, sizeof(*flv)); + if (!flv) + return NULL; + + flv->read = read; + flv->param = param; + if (0 != flv_read_header(flv)) + { + flv_reader_destroy(flv); + return NULL; + } + + return flv; +} + +void flv_reader_destroy(void* p) +{ + struct flv_reader_t* flv; + flv = (struct flv_reader_t*)p; + if (NULL != flv) + { + if (flv->fp) + fclose(flv->fp); + free(flv); + } +} + +int flv_reader_read(void* p, int* tagtype, uint32_t* timestamp, size_t* taglen, void* buffer, size_t bytes) +{ + int r; + uint32_t sz; + uint8_t header[FLV_TAG_HEADER_SIZE]; + struct flv_tag_header_t tag; + struct flv_reader_t* flv; + flv = (struct flv_reader_t*)p; + + r = flv->read(flv->param, &header, FLV_TAG_HEADER_SIZE); + if (r != FLV_TAG_HEADER_SIZE) + return r < 0 ? r : 0; // 0-EOF + + if (FLV_TAG_HEADER_SIZE != flv_tag_header_read(&tag, header, FLV_TAG_HEADER_SIZE)) + return -1; + + if (bytes < tag.size) + return -1; + + // FLV stream + r = flv->read(flv->param, buffer, tag.size); + if(tag.size != (uint32_t)r) + return r < 0 ? r : 0; // 0-EOF + + // PreviousTagSizeN + r = flv->read(flv->param, header, 4); + if (4 != r) + return r < 0 ? r : 0; // 0-EOF + + *taglen = tag.size; + *tagtype = tag.type; + *timestamp = tag.timestamp; + flv_tag_size_read(header, 4, &sz); + assert(0 == tag.streamId); // StreamID Always 0 + assert(sz == tag.size + FLV_TAG_HEADER_SIZE); + return (sz == tag.size + FLV_TAG_HEADER_SIZE) ? 1 : -1; +} diff --git a/MediaServer/libflv/source/flv-writer.c b/MediaServer/libflv/source/flv-writer.c new file mode 100644 index 0000000..37186e3 --- /dev/null +++ b/MediaServer/libflv/source/flv-writer.c @@ -0,0 +1,162 @@ +#include "flv-writer.h" +#include "flv-header.h" +#include "flv-proto.h" +#include +#include +#include +#include + +#define FLV_HEADER_SIZE 9 // DataOffset included +#define FLV_TAG_HEADER_SIZE 11 // StreamID included + +struct flv_writer_t +{ + FILE* fp; + flv_writer_onwrite write; + void* param; +}; + +static int flv_write_header(int audio, int video, struct flv_writer_t* flv) +{ + struct flv_vec_t vec[1]; + uint8_t header[FLV_HEADER_SIZE + 4]; + flv_header_write(audio, video, header, FLV_HEADER_SIZE); + flv_tag_size_write(header + FLV_HEADER_SIZE, 4, 0); // PreviousTagSize0(Always 0) + vec[0].ptr = header; + vec[0].len = sizeof(header); + return flv->write(flv->param, vec, 1); +} + +static int flv_write_eos(struct flv_writer_t* flv) +{ + int n; + uint8_t header[16]; + struct flv_video_tag_header_t video; + memset(&video, 0, sizeof(video)); + video.codecid = FLV_VIDEO_H264; + video.keyframe = FLV_VIDEO_KEY_FRAME; + video.avpacket = FLV_END_OF_SEQUENCE; + video.cts = 0; + + n = flv_video_tag_header_write(&video, header, sizeof(header)); + return n > 0 ? flv_writer_input(flv, FLV_TYPE_VIDEO, header, n, 0) : -1; +} + +static int file_write(void* param, const struct flv_vec_t* vec, int n) +{ + int i; + for(i = 0; i < n; i++) + { + if (vec[i].len != (int)fwrite(vec[i].ptr, 1, vec[i].len, (FILE*)param)) + return ferror((FILE*)param); + } + return 0; +} + +void* flv_writer_create(const char* file) +{ + FILE* fp; + struct flv_writer_t* flv; + fp = fopen(file, "wb"); + if (!fp) + return NULL; + + flv = flv_writer_create2(1, 1, file_write, fp); + if (!flv) + { + fclose(fp); + return NULL; + } + + flv->fp = fp; + return flv; +} + +void* flv_writer_create2(int audio, int video, flv_writer_onwrite write, void* param) +{ + struct flv_writer_t* flv; + flv = (struct flv_writer_t*)calloc(1, sizeof(*flv)); + if (!flv) + return NULL; + + flv->write = write; + flv->param = param; + if (0 != flv_write_header(audio, video, flv)) + { + flv_writer_destroy(flv); + return NULL; + } + + return flv; +} + +void flv_writer_destroy(void* p) +{ + struct flv_writer_t* flv; + flv = (struct flv_writer_t*)p; + + if (NULL != flv) + { + flv_write_eos(flv); + if (flv->fp) + fclose(flv->fp); + free(flv); + } +} + +int flv_writer_input(void* p, int type, const void* data, size_t bytes, uint32_t timestamp) +{ + uint8_t buf[FLV_TAG_HEADER_SIZE + 4]; + struct flv_vec_t vec[3]; + struct flv_writer_t* flv; + struct flv_tag_header_t tag; + flv = (struct flv_writer_t*)p; + + memset(&tag, 0, sizeof(tag)); + tag.size = (int)bytes; + tag.type = (uint8_t)type; + tag.timestamp = timestamp; + flv_tag_header_write(&tag, buf, FLV_TAG_HEADER_SIZE); + flv_tag_size_write(buf + FLV_TAG_HEADER_SIZE, 4, (uint32_t)bytes + FLV_TAG_HEADER_SIZE); + + vec[0].ptr = buf; // FLV Tag Header + vec[0].len = FLV_TAG_HEADER_SIZE; + vec[1].ptr = (void*)data; + vec[1].len = (int)bytes; + vec[2].ptr = buf + FLV_TAG_HEADER_SIZE; // TAG size + vec[2].len = 4; + return flv->write(flv->param, vec, 3); +} + +int flv_writer_input_v(void* p, int type, const struct flv_vec_t* v, int n, uint32_t timestamp) +{ + int i; + uint8_t buf[FLV_TAG_HEADER_SIZE + 4]; + struct flv_vec_t vec[8]; + struct flv_writer_t* flv; + struct flv_tag_header_t tag; + flv = (struct flv_writer_t*)p; + + memset(&tag, 0, sizeof(tag)); + tag.size = 0; + tag.type = (uint8_t)type; + tag.timestamp = timestamp; + + assert(n + 2 <= sizeof(vec) / sizeof(vec[0])); + for (i = 0; i < n && i + 2 < sizeof(vec)/sizeof(vec[0]); i++) + { + tag.size += v[i].len; + vec[i+1].ptr = v[i].ptr; + vec[i+1].len = v[i].len; + } + + vec[0].ptr = buf; // FLV Tag Header + vec[0].len = FLV_TAG_HEADER_SIZE; + vec[n + 1].ptr = buf + FLV_TAG_HEADER_SIZE; // TAG size + vec[n + 1].len = 4; + + flv_tag_header_write(&tag, buf, FLV_TAG_HEADER_SIZE); + flv_tag_size_write(buf + FLV_TAG_HEADER_SIZE, 4, (uint32_t)tag.size + FLV_TAG_HEADER_SIZE); + + return flv->write(flv->param, vec, n+2); +} diff --git a/MediaServer/libflv/source/hevc-annexbtomp4.c b/MediaServer/libflv/source/hevc-annexbtomp4.c new file mode 100644 index 0000000..5f55b36 --- /dev/null +++ b/MediaServer/libflv/source/hevc-annexbtomp4.c @@ -0,0 +1,474 @@ +#include "mpeg4-hevc.h" +#include "mpeg4-avc.h" +#include +#include +#include + +#define H265_NAL_BLA_W_LP 16 +#define H265_NAL_RSV_IRAP 23 +#define H265_NAL_VPS 32 +#define H265_NAL_SPS 33 +#define H265_NAL_PPS 34 +#define H265_NAL_AUD 35 +#define H265_NAL_SEI_PREFIX 39 +#define H265_NAL_SEI_SUFFIX 40 + +#define MAX(x, y) ((x) > (y) ? (x) : (y)) + +#define BIT(ptr, off) (((ptr)[(off) / 8] >> (7 - ((off) % 8))) & 0x01) + +struct h265_annexbtomp4_handle_t +{ + struct mpeg4_hevc_t* hevc; + int errcode; + int* update; // avc sps/pps update flags + int* vcl; + + uint8_t* out; + size_t bytes; + size_t capacity; +}; + +uint8_t mpeg4_h264_read_ue(const uint8_t* data, size_t bytes, size_t* offset); + +static size_t hevc_rbsp_decode(const uint8_t* nalu, size_t bytes, uint8_t* sodb, size_t len) +{ + size_t i, j; + const size_t max_sps_luma_bit_depth_offset = 256; + for (j = i = 0; i < bytes && j < len && i < max_sps_luma_bit_depth_offset; i++) + { + if (i + 2 < bytes && 0 == nalu[i] && 0 == nalu[i + 1] && 0x03 == nalu[i + 2]) + { + sodb[j++] = nalu[i]; + sodb[j++] = nalu[i + 1]; + i += 2; + } + else + { + sodb[j++] = nalu[i]; + } + } + return j; +} + +static int hevc_profile_tier_level(const uint8_t* nalu, size_t bytes, uint8_t maxNumSubLayersMinus1, struct mpeg4_hevc_t* hevc) +{ + size_t n; + uint8_t i; + uint8_t sub_layer_profile_present_flag[8]; + uint8_t sub_layer_level_present_flag[8]; + + if (bytes < 12) + return -1; + + hevc->general_profile_space = (nalu[0] >> 6) & 0x03; + hevc->general_tier_flag = (nalu[0] >> 5) & 0x01; + hevc->general_profile_idc = nalu[0] & 0x1f; + + hevc->general_profile_compatibility_flags = 0; + hevc->general_profile_compatibility_flags |= nalu[1] << 24; + hevc->general_profile_compatibility_flags |= nalu[2] << 16; + hevc->general_profile_compatibility_flags |= nalu[3] << 8; + hevc->general_profile_compatibility_flags |= nalu[4]; + + hevc->general_constraint_indicator_flags = 0; + hevc->general_constraint_indicator_flags |= ((uint64_t)nalu[5]) << 40; + hevc->general_constraint_indicator_flags |= ((uint64_t)nalu[6]) << 32; + hevc->general_constraint_indicator_flags |= ((uint64_t)nalu[7]) << 24; + hevc->general_constraint_indicator_flags |= ((uint64_t)nalu[8]) << 16; + hevc->general_constraint_indicator_flags |= ((uint64_t)nalu[9]) << 8; + hevc->general_constraint_indicator_flags |= nalu[10]; + + hevc->general_level_idc = nalu[11]; + if (maxNumSubLayersMinus1 < 1) + return 12; + + if (bytes < 14) + return -1; // error + + for (i = 0; i < maxNumSubLayersMinus1; i++) + { + sub_layer_profile_present_flag[i] = BIT(nalu, 12 * 8 + i * 2); + sub_layer_level_present_flag[i] = BIT(nalu, 12 * 8 + i * 2 + 1); + } + + n = 12 + 2; + for (i = 0; i < maxNumSubLayersMinus1; i++) + { + if(sub_layer_profile_present_flag[i]) + n += 11; + if (sub_layer_level_present_flag[i]) + n += 1; + } + + return bytes >= n ? (int)n : -1; +} + +static uint8_t hevc_vps_id(const uint8_t* rbsp, size_t bytes, struct mpeg4_hevc_t* hevc, uint8_t* ptr, size_t len) +{ + size_t sodb; + uint8_t vps; + uint8_t vps_max_sub_layers_minus1; + uint8_t vps_temporal_id_nesting_flag; + + sodb = hevc_rbsp_decode(rbsp, bytes, ptr, len); + if (sodb < 16 + 2) + return 0xFF; + + vps = ptr[2] >> 4; // 2-nalu type + vps_max_sub_layers_minus1 = (ptr[3] >> 1) & 0x07; + vps_temporal_id_nesting_flag = ptr[3] & 0x01; + hevc->numTemporalLayers = MAX(hevc->numTemporalLayers, vps_max_sub_layers_minus1 + 1); + hevc->temporalIdNested = (hevc->temporalIdNested || vps_temporal_id_nesting_flag) ? 1 : 0; + hevc_profile_tier_level(ptr + 6, sodb - 6, vps_max_sub_layers_minus1, hevc); + + return vps; +} + +static uint8_t hevc_sps_id(const uint8_t* rbsp, size_t bytes, struct mpeg4_hevc_t* hevc, uint8_t* ptr, size_t len, uint8_t* vps) +{ + size_t n; + size_t sodb; + uint8_t sps; + uint8_t sps_max_sub_layers_minus1; + uint8_t sps_temporal_id_nesting_flag; + uint8_t conformance_window_flag; + + sodb = hevc_rbsp_decode(rbsp, bytes, ptr, len); + if (sodb < 12+3) + return 0xFF; + + *vps = ptr[2] >> 4; // 2-nalu type + sps_max_sub_layers_minus1 = (ptr[2] >> 1) & 0x07; + sps_temporal_id_nesting_flag = ptr[2] & 0x01; + n = hevc_profile_tier_level(ptr + 3, sodb - 3, sps_max_sub_layers_minus1, hevc); + if (n <= 0) + return 0xFF; + + n = (n + 3) * 8; + sps = mpeg4_h264_read_ue(ptr, sodb, &n); + hevc->chromaFormat = mpeg4_h264_read_ue(ptr, sodb, &n); + if (3 == hevc->chromaFormat) + n++; + mpeg4_h264_read_ue(ptr, sodb, &n); // pic_width_in_luma_samples + mpeg4_h264_read_ue(ptr, sodb, &n); // pic_height_in_luma_samples + conformance_window_flag = BIT(ptr, n); n++; // conformance_window_flag + if (conformance_window_flag) + { + mpeg4_h264_read_ue(ptr, sodb, &n); // conf_win_left_offset + mpeg4_h264_read_ue(ptr, sodb, &n); // conf_win_right_offset + mpeg4_h264_read_ue(ptr, sodb, &n); // conf_win_top_offset + mpeg4_h264_read_ue(ptr, sodb, &n); // conf_win_bottom_offset + } + hevc->bitDepthLumaMinus8 = mpeg4_h264_read_ue(ptr, sodb, &n); + hevc->bitDepthChromaMinus8 = mpeg4_h264_read_ue(ptr, sodb, &n); + + // TODO: vui_parameters + //mp4->hevc->min_spatial_segmentation_idc; // min_spatial_segmentation_idc + return sps; +} + +static uint8_t hevc_pps_id(const uint8_t* rbsp, size_t bytes, struct mpeg4_hevc_t* hevc, uint8_t* ptr, size_t len, uint8_t* sps) +{ + uint8_t pps; + size_t sodb; + size_t offset = 2 * 8; // 2-nalu type + sodb = hevc_rbsp_decode(rbsp, bytes, ptr, len); + if (sodb < 3) + return 0xFF; (void)hevc; + pps = mpeg4_h264_read_ue(ptr, sodb, &offset); + *sps = mpeg4_h264_read_ue(ptr, sodb, &offset); + return pps; +} + +static void mpeg4_hevc_remove(struct mpeg4_hevc_t* hevc, uint8_t* ptr, size_t bytes, const uint8_t* end) +{ + uint8_t i; + assert(ptr >= hevc->data && ptr + bytes <= end && end <= hevc->data + sizeof(hevc->data)); + memmove(ptr, ptr + bytes, end - ptr - bytes); + + for (i = 0; i < hevc->numOfArrays; i++) + { + if (hevc->nalu[i].data > ptr) + hevc->nalu[i].data -= bytes; + } +} + +static int mpeg4_hevc_update2(struct mpeg4_hevc_t* hevc, int i, const uint8_t* nalu, size_t bytes) +{ + if (bytes == hevc->nalu[i].bytes && 0 == memcmp(nalu, hevc->nalu[i].data, bytes)) + return 0; // do nothing + + if (bytes > hevc->nalu[i].bytes && hevc->off + (bytes - hevc->nalu[i].bytes) > sizeof(hevc->data)) + { + assert(0); + return -1; // too big + } + + mpeg4_hevc_remove(hevc, hevc->nalu[i].data, hevc->nalu[i].bytes, hevc->data + hevc->off); + hevc->off -= hevc->nalu[i].bytes; + + hevc->nalu[i].data = hevc->data + hevc->off; + hevc->nalu[i].bytes = (uint16_t)bytes; + memcpy(hevc->nalu[i].data, nalu, bytes); + hevc->off += bytes; + return 1; +} + +static int mpeg4_hevc_add(struct mpeg4_hevc_t* hevc, uint8_t type, const uint8_t* nalu, size_t bytes) +{ + // copy new + assert(hevc->numOfArrays < sizeof(hevc->nalu) / sizeof(hevc->nalu[0])); + if (hevc->numOfArrays >= sizeof(hevc->nalu) / sizeof(hevc->nalu[0]) + || hevc->off + bytes > sizeof(hevc->data)) + { + assert(0); + return -1; + } + + hevc->nalu[hevc->numOfArrays].type = type; + hevc->nalu[hevc->numOfArrays].bytes = (uint16_t)bytes; + hevc->nalu[hevc->numOfArrays].array_completeness = 1; + hevc->nalu[hevc->numOfArrays].data = hevc->data + hevc->off; + memcpy(hevc->nalu[hevc->numOfArrays].data, nalu, bytes); + hevc->off += bytes; + ++hevc->numOfArrays; + return 1; +} + +static int h265_vps_copy(struct mpeg4_hevc_t* hevc, const uint8_t* nalu, size_t bytes) +{ + int i; + uint8_t vpsid; + + if (bytes < 3) + { + assert(0); + return -1; // invalid length + } + + vpsid = hevc_vps_id(nalu, bytes, hevc, hevc->data + hevc->off, sizeof(hevc->data)-hevc->off); + for (i = 0; i < hevc->numOfArrays; i++) + { + if (H265_NAL_VPS == hevc->nalu[i].type && vpsid == hevc_vps_id(hevc->nalu[i].data, hevc->nalu[i].bytes, hevc, hevc->data + hevc->off, sizeof(hevc->data) - hevc->off)) + return mpeg4_hevc_update2(hevc, i, nalu, bytes); + } + + return mpeg4_hevc_add(hevc, H265_NAL_VPS, nalu, bytes); +} + +static int h265_sps_copy(struct mpeg4_hevc_t* hevc, const uint8_t* nalu, size_t bytes) +{ + int i; + uint8_t spsid; + uint8_t vpsid, vpsid2; + + if (bytes < 13 + 2) + { + assert(0); + return -1; // invalid length + } + + spsid = hevc_sps_id(nalu, bytes, hevc, hevc->data + hevc->off, sizeof(hevc->data) - hevc->off, &vpsid); + for (i = 0; i < hevc->numOfArrays; i++) + { + if (H265_NAL_SPS == hevc->nalu[i].type && spsid == hevc_sps_id(hevc->nalu[i].data, hevc->nalu[i].bytes, hevc, hevc->data + hevc->off, sizeof(hevc->data) - hevc->off, &vpsid2) && vpsid == vpsid2) + return mpeg4_hevc_update2(hevc, i, nalu, bytes); + } + + return mpeg4_hevc_add(hevc, H265_NAL_SPS, nalu, bytes); +} + +static int h265_pps_copy(struct mpeg4_hevc_t* hevc, const uint8_t* nalu, size_t bytes) +{ + int i; + uint8_t ppsid; + uint8_t spsid, spsid2; + + if (bytes < 1 + 2) + { + assert(0); + return -1; // invalid length + } + + ppsid = hevc_pps_id(nalu, bytes, hevc, hevc->data + hevc->off, sizeof(hevc->data) - hevc->off, &spsid); + for (i = 0; i < hevc->numOfArrays; i++) + { + if (H265_NAL_PPS == hevc->nalu[i].type && ppsid == hevc_pps_id(hevc->nalu[i].data, hevc->nalu[i].bytes, hevc, hevc->data + hevc->off, sizeof(hevc->data) - hevc->off, &spsid2) && spsid == spsid2) + return mpeg4_hevc_update2(hevc, i, nalu, bytes); + } + + return mpeg4_hevc_add(hevc, H265_NAL_PPS, nalu, bytes); +} + +static int h265_sei_clear(struct mpeg4_hevc_t* hevc) +{ + int i; + for (i = 0; i < hevc->numOfArrays; i++) + { + if (H265_NAL_SEI_PREFIX == hevc->nalu[i].type || H265_NAL_SEI_SUFFIX == hevc->nalu[i].type) + { + mpeg4_hevc_remove(hevc, hevc->nalu[i].data, hevc->nalu[i].bytes, hevc->data + hevc->off); + hevc->off -= hevc->nalu[i].bytes; + if(i + 1 < hevc->numOfArrays) + memmove(hevc->nalu + i, hevc->nalu + i + 1, sizeof(hevc->nalu[0]) * (hevc->numOfArrays - i - 1)); + --hevc->numOfArrays; + --i; + } + } + return 0; +} + +int mpeg4_hevc_update(struct mpeg4_hevc_t* hevc, const uint8_t* nalu, size_t bytes) +{ + int r; + + switch ((nalu[0] >> 1) & 0x3f) + { + case H265_NAL_VPS: + h265_sei_clear(hevc); // remove all prefix/suffix sei + r = h265_vps_copy(hevc, nalu, bytes); + break; + + case H265_NAL_SPS: + r = h265_sps_copy(hevc, nalu, bytes); + break; + + case H265_NAL_PPS: + r = h265_pps_copy(hevc, nalu, bytes); + break; + +#if defined(H265_FILTER_SEI) + case H265_NAL_SEI_PREFIX: + r = mpeg4_hevc_add(hevc, H265_NAL_SEI_PREFIX, nalu, bytes); + break; + + case H265_NAL_SEI_SUFFIX: + r = mpeg4_hevc_add(hevc, H265_NAL_SEI_SUFFIX, nalu, bytes); + break; +#endif + + default: + r = 0; + break; + } + + return r; +} + +static void hevc_handler(void* param, const uint8_t* nalu, size_t bytes) +{ + int r; + uint8_t nalutype; + struct h265_annexbtomp4_handle_t* mp4; + mp4 = (struct h265_annexbtomp4_handle_t*)param; + + if (bytes < 2) + { + assert(0); + return; + } + + nalutype = (nalu[0] >> 1) & 0x3f; +#if defined(H2645_FILTER_AUD) + if(H265_NAL_AUD == nalutype) + return; // ignore AUD +#endif + + r = mpeg4_hevc_update(mp4->hevc, nalu, bytes); + if (1 == r && mp4->update) + *mp4->update = 1; + else if (r < 0) + mp4->errcode = r; + + // IRAP-1, B/P-2, other-0 + if (mp4->vcl && nalutype < H265_NAL_VPS) + *mp4->vcl = H265_NAL_BLA_W_LP<=nalutype && nalutype<=H265_NAL_RSV_IRAP ? 1 : 2; + + if (mp4->capacity >= mp4->bytes + bytes + 4) + { + mp4->out[mp4->bytes + 0] = (uint8_t)((bytes >> 24) & 0xFF); + mp4->out[mp4->bytes + 1] = (uint8_t)((bytes >> 16) & 0xFF); + mp4->out[mp4->bytes + 2] = (uint8_t)((bytes >> 8) & 0xFF); + mp4->out[mp4->bytes + 3] = (uint8_t)((bytes >> 0) & 0xFF); + memmove(mp4->out + mp4->bytes + 4, nalu, bytes); + mp4->bytes += bytes + 4; + } + else + { + mp4->errcode = -1; + } +} + +int h265_annexbtomp4(struct mpeg4_hevc_t* hevc, const void* data, size_t bytes, void* out, size_t size, int *vcl, int* update) +{ + struct h265_annexbtomp4_handle_t h; + memset(&h, 0, sizeof(h)); + h.hevc = hevc; + h.vcl = vcl; + h.update = update; + h.out = (uint8_t*)out; + h.capacity = size; + if (vcl) *vcl = 0; + if (update) *update = 0; + +// hevc->numTemporalLayers = 0; +// hevc->temporalIdNested = 0; +// hevc->min_spatial_segmentation_idc = 0; +// hevc->general_profile_compatibility_flags = 0xffffffff; +// hevc->general_constraint_indicator_flags = 0xffffffffffULL; +// hevc->chromaFormat = 1; // 4:2:0 + + mpeg4_h264_annexb_nalu((const uint8_t*)data, bytes, hevc_handler, &h); + hevc->configurationVersion = 1; + hevc->lengthSizeMinusOne = 3; // 4 bytes + return 0 == h.errcode ? (int)h.bytes : 0; +} + +int h265_is_new_access_unit(const uint8_t* nalu, size_t bytes) +{ + enum { NAL_VPS = 32, NAL_SPS = 33, NAL_PPS = 34, NAL_AUD = 35, NAL_PREFIX_SEI = 39, }; + + uint8_t nal_type; + uint8_t nuh_layer_id; + + if(bytes < 3) + return 0; + + nal_type = (nalu[0] >> 1) & 0x3f; + nuh_layer_id = ((nalu[0] & 0x01) << 5) | ((nalu[1] >> 3) &0x1F); + + // 7.4.2.4.4 Order of NAL units and coded pictures and their association to access units + if(NAL_VPS == nal_type || NAL_SPS == nal_type || NAL_PPS == nal_type || + (nuh_layer_id == 0 && (NAL_AUD == nal_type || NAL_PREFIX_SEI == nal_type || (41 <= nal_type && nal_type <= 44) || (48 <= nal_type && nal_type <= 55)))) + return 1; + + // 7.4.2.4.5 Order of VCL NAL units and association to coded pictures + if (nal_type <= 31) + { + //first_slice_segment_in_pic_flag 0x80 + return (nalu[2] & 0x80) ? 1 : 0; + } + + return 0; +} + +#if defined(_DEBUG) || defined(DEBUG) +void hevc_annexbtomp4_test(void) +{ + const uint8_t vps[] = { 0x40, 0x01, 0x0c, 0x01, 0xff, 0xff, 0x01, 0x60, 0x00, 0x00, 0x03, 0x00, 0x80, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x78, 0x9d, 0xc0, 0x90 }; + const uint8_t sps[] = { 0x42, 0x01, 0x01, 0x01, 0x60, 0x00, 0x00, 0x03, 0x00, 0x80, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x78, 0xa0, 0x03, 0xc0, 0x80, 0x32, 0x16, 0x59, 0xde, 0x49, 0x1b, 0x6b, 0x80, 0x40, 0x00, 0x00, 0xfa, 0x00, 0x00, 0x17, 0x70, 0x02 }; + const uint8_t pps[] = { 0x44, 0x01, 0xc1, 0x73, 0xd1, 0x89 }; + const uint8_t annexb[] = { 0x00, 0x00, 0x00, 0x01, 0x4e, 0x01, 0x06, 0x01, 0xd0, 0x80, 0x00, 0x00, 0x00, 0x01, 0x40, 0x01, 0x0c, 0x01, 0xff, 0xff, 0x01, 0x60, 0x00, 0x00, 0x03, 0x00, 0x80, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x78, 0x9d, 0xc0, 0x90, 0x00, 0x00, 0x00, 0x01, 0x42, 0x01, 0x01, 0x01, 0x60, 0x00, 0x00, 0x03, 0x00, 0x80, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x78, 0xa0, 0x03, 0xc0, 0x80, 0x32, 0x16, 0x59, 0xde, 0x49, 0x1b, 0x6b, 0x80, 0x40, 0x00, 0x00, 0xfa, 0x00, 0x00, 0x17, 0x70, 0x02, 0x00, 0x00, 0x00, 0x01, 0x44, 0x01, 0xc1, 0x73, 0xd1, 0x89 }; + uint8_t output[512]; + int vcl, update; + + struct mpeg4_hevc_t hevc; + memset(&hevc, 0, sizeof(hevc)); + assert(h265_annexbtomp4(&hevc, annexb, sizeof(annexb), output, sizeof(output), &vcl, &update) > 0); + assert(3 == hevc.numOfArrays && vcl == 0 && update == 1); + assert(hevc.nalu[0].bytes == sizeof(vps) && 0 == memcmp(hevc.nalu[0].data, vps, sizeof(vps))); + assert(hevc.nalu[1].bytes == sizeof(sps) && 0 == memcmp(hevc.nalu[1].data, sps, sizeof(sps))); + assert(hevc.nalu[2].bytes == sizeof(pps) && 0 == memcmp(hevc.nalu[2].data, pps, sizeof(pps))); +} +#endif diff --git a/MediaServer/libflv/source/hevc-mp4toannexb.c b/MediaServer/libflv/source/hevc-mp4toannexb.c new file mode 100644 index 0000000..764473e --- /dev/null +++ b/MediaServer/libflv/source/hevc-mp4toannexb.c @@ -0,0 +1,134 @@ +#include "mpeg4-hevc.h" +#include "mpeg4-avc.h" +#include +#include +#include +#include +#include + +#define H265_NAL_BLA_W_LP 16 +#define H265_NAL_RSV_IRAP 23 +#define H265_NAL_VPS 32 +#define H265_NAL_SPS 33 +#define H265_NAL_PPS 34 +#define H265_NAL_AUD 35 // Access unit delimiter + +struct h265_mp4toannexb_handle_t +{ + const struct mpeg4_hevc_t* hevc; + int vps_sps_pps_flag; + int errcode; + + uint8_t* out; + size_t bytes; + size_t capacity; +}; + +static int h265_vps_sps_pps_size(const struct mpeg4_hevc_t* hevc) +{ + int i, n = 0; + for (i = 0; i < hevc->numOfArrays; i++) + n += hevc->nalu[i].bytes + 4; + return n; +} + +static void h265_mp4toannexb_handler(void* param, const uint8_t* nalu, size_t bytes) +{ + int n; + uint8_t irap, nalu_type; + const uint8_t h265_start_code[] = { 0x00, 0x00, 0x00, 0x01 }; + struct h265_mp4toannexb_handle_t* mp4; + mp4 = (struct h265_mp4toannexb_handle_t*)param; + + if (bytes < 1) + { + assert(0); + mp4->errcode = -EINVAL; + return; + } + + nalu_type = (nalu[0] >> 1) & 0x3F; +#if defined(H2645_FILTER_AUD) + if (H265_NAL_AUD == nalu_type) + continue; // ignore AUD +#endif + + if (H265_NAL_VPS == nalu_type || H265_NAL_SPS == nalu_type || H265_NAL_PPS == nalu_type) + mp4->vps_sps_pps_flag = 1; + + irap = H265_NAL_BLA_W_LP <= nalu_type && nalu_type <= H265_NAL_RSV_IRAP; + if (irap && 0 == mp4->vps_sps_pps_flag) + { + // insert VPS/SPS/PPS before IDR frame + if (mp4->bytes > 0) + { + // write sps/pps at first + n = h265_vps_sps_pps_size(mp4->hevc); + if (n + mp4->bytes > mp4->capacity) + { + mp4->errcode = -E2BIG; + return; + } + memmove(mp4->out + n, mp4->out, mp4->bytes); + } + + n = mpeg4_hevc_to_nalu(mp4->hevc, mp4->out, mp4->capacity); + if (n <= 0) + { + mp4->errcode = 0 == n ? -EINVAL : n; + return; + } + mp4->bytes += n; + mp4->vps_sps_pps_flag = 1; + } + + if (mp4->bytes + bytes + sizeof(h265_start_code) > mp4->capacity) + { + mp4->errcode = -E2BIG; + return; + } + + memcpy(mp4->out + mp4->bytes, h265_start_code, sizeof(h265_start_code)); + memcpy(mp4->out + mp4->bytes + sizeof(h265_start_code), nalu, bytes); + mp4->bytes += sizeof(h265_start_code) + bytes; +} + +int h265_mp4toannexb(const struct mpeg4_hevc_t* hevc, const void* data, size_t bytes, void* out, size_t size) +{ + int i, n; + const uint8_t* src, * end; + struct h265_mp4toannexb_handle_t h; + + memset(&h, 0, sizeof(h)); + h.hevc = hevc; + h.out = (uint8_t*)out; + h.capacity = size; + + end = (uint8_t*)data + bytes; + for(src = (uint8_t*)data; src + hevc->lengthSizeMinusOne + 1 < end; src += n) + { + for (n = i = 0; i < hevc->lengthSizeMinusOne + 1; i++) + n = (n << 8) + ((uint8_t*)src)[i]; + + // fix 0x00 00 00 01 => flv nalu size + if (0 == hevc->lengthSizeMinusOne || (1 == n && (2 == hevc->lengthSizeMinusOne || 3 == hevc->lengthSizeMinusOne))) + { + //n = (int)(end - src) - avc->nalu; + mpeg4_h264_annexb_nalu(src, end - src, h265_mp4toannexb_handler, &h); + src = end; + break; + } + + src += hevc->lengthSizeMinusOne + 1; + if (n < 1 || src + n > end) + { + assert(0); + return -EINVAL; + } + + h265_mp4toannexb_handler(&h, src, n); + } + + assert(src == end); + return 0 == h.errcode ? (int)h.bytes : 0; +} diff --git a/MediaServer/libflv/source/mp3-header.c b/MediaServer/libflv/source/mp3-header.c new file mode 100644 index 0000000..fd69765 --- /dev/null +++ b/MediaServer/libflv/source/mp3-header.c @@ -0,0 +1,233 @@ +#include "mp3-header.h" +#include +#include +#include + +// layer-1, layer-2, layer-3 +static int s_bitrate_mpeg1[3][16] = { + { 0/*free*/, 32000, 64000, 96000, 128000, 160000, 192000, 224000, 256000, 288000, 320000, 352000, 384000, 416000, 448000, -1 }, + { 0/*free*/, 32000, 48000, 56000, 64000, 80000, 96000, 112000, 128000, 160000, 192000, 224000, 256000, 320000, 384000, -1 }, + { 0/*free*/, 32000, 40000, 48000, 56000, 64000, 80000, 96000, 112000, 128000, 160000, 192000, 224000, 256000, 320000, -1 }, +}; + +// layer-1, layer-2, layer-3 +static int s_bitrate_mpeg2[3][16] = { + { 0/*free*/, 32000, 48000, 56000, 64000, 80000, 96000, 112000, 128000, 144000, 160000, 176000, 192000, 224000, 256000, -1 }, + { 0/*free*/, 8000, 16000, 24000, 32000, 40000, 48000, 56000, 64000, 80000, 96000, 112000, 128000, 144000, 160000, -1 }, + { 0/*free*/, 8000, 16000, 24000, 32000, 40000, 48000, 56000, 64000, 80000, 96000, 112000, 128000, 144000, 160000, -1 }, +}; + +// layer-1, layer-2, layer-3 +static int s_frequency_mpeg1[4] = { 44100, 48000, 32000, -1 }; +static int s_frequency_mpeg2[4] = { 22050, 24000, 16000, -1 }; +static int s_frequency_mpeg25[4] = { 11025, 12000, 8000, -1 }; + +// layer-1, layer-2, layer-3 +//static int s_frames_mpeg1[3] = { 384, 1152, 1152 }; +//static int s_frames_mpeg2[3] = { 384, 1152, 576 }; +//static int s_frames_mpeg25[3] = { 384, 1152, 576 }; + +// layer-1 bytes = ((frames / 8 * bitrate) / frequency + padding * 4 +// layer-2/3 bytes = ((frames / 8 * bitrate) / frequency + padding + +int mp3_header_load(struct mp3_header_t* mp3, const void* data, int bytes) +{ + const uint8_t* p; + if (bytes < 4) + return 0; + + p = data; + if (0 == memcmp("TAG", p, 3)) + { + if (bytes < 128/*ID3v1*/ + 4) + return 0; + p += 128; + } + else if (0 == memcmp("ID3", p, 3)) + { + uint32_t n; + if (3 != p[3]/*version*/ || bytes < 10) + return 0; + n = (((uint32_t)p[6] & 0x7F) << 21) | (((uint32_t)p[7] & 0x7F) << 14) | (((uint32_t)p[8] & 0x7F) << 7) | (p[9] & 0x7F); + if (bytes < (int)n + 10) + return 0; + p += n + 10; + } + + //sync: 1111 1111 111 + if (0xFF != p[0] || 0xE0 != (p[1] & 0xE0)) + { + assert(0); + return 0; + } + + mp3->version = (p[1] >> 3) & 0x03; + mp3->layer = (p[1] >> 1) & 0x03; + mp3->protection = p[1] & 0x01; + mp3->bitrate_index = (p[2] >> 4) & 0x0F; + mp3->sampling_frequency = (p[2] >> 2) & 0x03; + mp3->priviate = p[2] & 0x01; + mp3->mode = (p[3] >> 6) & 0x03; + mp3->mode_extension = (p[3] >> 4) & 0x03; + mp3->copyright = (p[3] >> 3) & 0x01; + mp3->original = (p[3] >> 2) & 0x01; + mp3->emphasis = p[3] & 0x03; + + return (int)(p - (uint8_t*)data) + 4; +} + +int mp3_header_save(const struct mp3_header_t* mp3, void* data, int bytes) +{ + uint8_t* p; + if (bytes < 4) + return 0; + + p = data; + p[0] = 0xFF; + p[1] = (uint8_t)(0xE0 | (mp3->version << 3) | (mp3->layer << 1) | mp3->protection); + p[2] = (uint8_t)((mp3->bitrate_index << 4) | (mp3->sampling_frequency << 2) | 0x00 /*padding*/ | mp3->priviate); + p[3] = (uint8_t)((mp3->mode << 6) | (mp3->mode_extension << 4) | (mp3->copyright << 3) | (mp3->original << 2) | mp3->emphasis); + return 4; +} + +int mp3_get_channel(const struct mp3_header_t* mp3) +{ + return 0x03 == mp3->mode ? 1 : 2; +} + +int mp3_get_bitrate(const struct mp3_header_t* mp3) +{ + if (mp3->layer < 1 || mp3->layer > 3) + { + assert(0); + return -1; + } + + switch (mp3->version) + { + case MP3_MPEG1: + return s_bitrate_mpeg1[3 - mp3->layer][mp3->bitrate_index]; + + case MP3_MPEG2: + case MP3_MPEG2_5: + return s_bitrate_mpeg2[3 - mp3->layer][mp3->bitrate_index]; + + default: + assert(0); + return -1; + } +} + +static int mp3_find_bitrate(const int* arr, int bitrate) +{ + int i; + for (i = 0; i < 16; i++) + { + if (bitrate == arr[i]) + return i; + } + return -1; +} + +int mp3_set_bitrate(struct mp3_header_t* mp3, int bitrate) +{ + int r; + if (mp3->layer < 1 || mp3->layer > 3) + { + assert(0); + return -1; + } + + switch (mp3->version) + { + case MP3_MPEG1: + r = mp3_find_bitrate(s_bitrate_mpeg1[3 - mp3->layer], bitrate); + break; + + case MP3_MPEG2: + case MP3_MPEG2_5: + r = mp3_find_bitrate(s_bitrate_mpeg2[3 - mp3->layer], bitrate); + break; + + default: + assert(0); + r = -1; + } + + if (-1 == r) + return -1; + + mp3->bitrate_index = (unsigned int)r; + return 0; +} + +int mp3_get_frequency(const struct mp3_header_t* mp3) +{ + if (mp3->sampling_frequency < 0 || mp3->sampling_frequency > 3) + return -1; + + switch (mp3->version) + { + case MP3_MPEG1: return s_frequency_mpeg1[mp3->sampling_frequency]; + case MP3_MPEG2: return s_frequency_mpeg2[mp3->sampling_frequency]; + case MP3_MPEG2_5: return s_frequency_mpeg25[mp3->sampling_frequency]; + default: assert(0); return -1; + } +} + +static int mp3_find_frequency(const int* arr, int frequency) +{ + int i; + for (i = 0; i < 4; i++) + { + if (frequency == arr[i]) + return i; + } + return -1; +} + +int mp3_set_frequency(struct mp3_header_t* mp3, int frequency) +{ + int r; + switch (mp3->version) + { + case MP3_MPEG1: + r = mp3_find_frequency(s_frequency_mpeg1, frequency); + break; + + case MP3_MPEG2: + r = mp3_find_frequency(s_frequency_mpeg2, frequency); + break; + + case MP3_MPEG2_5: + r = mp3_find_frequency(s_frequency_mpeg25, frequency); + break; + + default: + assert(0); + r = -1; + } + + if (-1 == r) + return -1; + + mp3->sampling_frequency = (unsigned int)r; + return 0; +} + +#if defined(DEBUG) || defined(_DEBUG) +void mp3_header_test(void) +{ + uint8_t v[4] = { 0xff, 0xfb, 0xe0, 0x64 }; + uint8_t v2[4]; + struct mp3_header_t mp3; + + assert(4 == mp3_header_load(&mp3, v, 4)); + assert(MP3_MPEG1 == mp3.version && MP3_LAYER3 == mp3.layer); + assert(14 == mp3.bitrate_index && 320000 == mp3_get_bitrate(&mp3)); + assert(0 == mp3.sampling_frequency && 44100 == mp3_get_frequency(&mp3)); + assert(1 == mp3.mode && 1 == mp3.protection); + assert(4 == mp3_header_save(&mp3, v2, 4)); + assert(0 == memcmp(v, v2, 4)); +} +#endif diff --git a/MediaServer/libflv/source/mpeg4-aac-asc.c b/MediaServer/libflv/source/mpeg4-aac-asc.c new file mode 100644 index 0000000..498f3e4 --- /dev/null +++ b/MediaServer/libflv/source/mpeg4-aac-asc.c @@ -0,0 +1,664 @@ +#include "mpeg4-aac.h" +#include "mpeg4-bits.h" +#include +#include +#include +#include + +// Table 4.85 - Syntactic elements (p533) +enum { + ID_SCE = 0x0, // single channel element() + ID_CPE = 0x1, // channel_pair_element() + ID_CCE = 0x2, // coupling_channel_element() + ID_LFE = 0x3, // lfe_channel_element() + ID_DSE = 0x4, // data_stream_element() + ID_PCE = 0x5, // program_config_element() + ID_FIL = 0x6, // fill_element() + ID_END = 0x7, +}; + +// ISO-14496-3 4.4.1.1 Program config element (p488) +// There may be up to 16 such elements per raw data block, each one must have a unique element_instance_tag +// PCEs must come before all other syntactic elements in a raw_data_block(). +/* +program_config_element() +{ + element_instance_tag; 4 uimsbf + object_type; 2 uimsbf + sampling_frequency_index; 4 uimsbf + num_front_channel_elements; 4 uimsbf + num_side_channel_elements; 4 uimsbf + num_back_channel_elements; 4 uimsbf + num_lfe_channel_elements; 2 uimsbf + num_assoc_data_elements; 3 uimsbf + num_valid_cc_elements; 4 uimsbf + mono_mixdown_present; 1 uimsbf + if (mono_mixdown_present == 1 ) + mono_mixdown_element_number; 4 uimsbf + stereo_mixdown_present; 1 uimsbf + if (stereo_mixdown_present == 1 ) + stereo_mixdown_element_number; 4 uimsbf + matrix_mixdown_idx_present; 1 uimsbf + if (matrix_mixdown_idx_present == 1 ) { + matrix_mixdown_idx ; 2 uimsbf + pseudo_surround_enable; 1 uimsbf + } + + for (i = 0; i < num_front_channel_elements; i++) { + front_element_is_cpe[i]; 1 bslbf + front_element_tag_select[i]; 4 uimsbf + } + for (i = 0; i < num_side_channel_elements; i++) { + side_element_is_cpe[i]; 1 bslbf + side_element_tag_select[i]; 4 uimsbf + } + for (i = 0; i < num_back_channel_elements; i++) { + back_element_is_cpe[i]; 1 bslbf + back_element_tag_select[i]; 4 uimsbf + } + for (i = 0; i < num_lfe_channel_elements; i++) + lfe_element_tag_select[i]; 4 uimsbf + for ( i = 0; i < num_assoc_data_elements; i++) + assoc_data_element_tag_select[i]; 4 uimsbf + for (i = 0; i < num_valid_cc_elements; i++) { + cc_element_is_ind_sw[i]; 1 uimsbf + valid_cc_element_tag_select[i]; 4 uimsbf + } + byte_alignment(); Note 1 + comment_field_bytes; 8 uimsbf + for (i = 0; i < comment_field_bytes; i++) + comment_field_data[i]; 8 uimsbf +} +*/ +static inline uint64_t mpeg4_bits_copy(struct mpeg4_bits_t* dst, struct mpeg4_bits_t* src, int n) +{ + uint64_t v; + v = mpeg4_bits_read_n(src, n); + mpeg4_bits_write_n(dst, v, n); + return v; +} + +static int mpeg4_aac_pce_load(struct mpeg4_bits_t* bits, struct mpeg4_aac_t* aac, struct mpeg4_bits_t* pce) +{ + uint64_t i, cpe, tag; + uint64_t element_instance_tag; + uint64_t object_type; + uint64_t sampling_frequency_index; + uint64_t num_front_channel_elements; + uint64_t num_side_channel_elements; + uint64_t num_back_channel_elements; + uint64_t num_lfe_channel_elements; + uint64_t num_assoc_data_elements; + uint64_t num_valid_cc_elements; + uint64_t comment_field_bytes; + + aac->channels = 0; + element_instance_tag = mpeg4_bits_copy(pce, bits, 4); + object_type = mpeg4_bits_copy(pce, bits, 2); + sampling_frequency_index = mpeg4_bits_copy(pce, bits, 4); + num_front_channel_elements = mpeg4_bits_copy(pce, bits, 4); + num_side_channel_elements = mpeg4_bits_copy(pce, bits, 4); + num_back_channel_elements = mpeg4_bits_copy(pce, bits, 4); + num_lfe_channel_elements = mpeg4_bits_copy(pce, bits, 2); + num_assoc_data_elements = mpeg4_bits_copy(pce, bits, 3); + num_valid_cc_elements = mpeg4_bits_copy(pce, bits, 4); + + if (mpeg4_bits_copy(pce, bits, 1)) + mpeg4_bits_copy(pce, bits, 4); // MONO + if (mpeg4_bits_copy(pce, bits, 1)) + mpeg4_bits_copy(pce, bits, 4); // STEREO + if (mpeg4_bits_copy(pce, bits, 1)) + mpeg4_bits_copy(pce, bits, 3); // Matrix, Pseudo surround + + for (i = 0; i < num_front_channel_elements; i++) + { + cpe = mpeg4_bits_copy(pce, bits, 1); // front_element_is_cpe + tag = mpeg4_bits_copy(pce, bits, 4); // front_element_tag_select + aac->channels += (cpe || aac->ps) ? 2 : 1; + } + + for (i = 0; i < num_side_channel_elements; i++) + { + cpe = mpeg4_bits_copy(pce, bits, 1); // side_element_is_cpe + tag = mpeg4_bits_copy(pce, bits, 4); // side_element_tag_select + aac->channels += (cpe || aac->ps) ? 2 : 1; + } + + for (i = 0; i < num_back_channel_elements; i++) + { + cpe = mpeg4_bits_copy(pce, bits, 1); // back_element_is_cpe + tag = mpeg4_bits_copy(pce, bits, 4); // back_element_tag_select + aac->channels += (cpe || aac->ps) ? 2 : 1; + } + + for (i = 0; i < num_lfe_channel_elements; i++) + { + tag = mpeg4_bits_copy(pce, bits, 4); // lfe_element_tag_select + aac->channels += 1; + } + + for (i = 0; i < num_assoc_data_elements; i++) + { + tag = mpeg4_bits_copy(pce, bits, 4); // assoc_data_element_tag_select + } + + for (i = 0; i < num_valid_cc_elements; i++) + { + cpe = mpeg4_bits_copy(pce, bits, 1); // cc_element_is_ind_sw + tag = mpeg4_bits_copy(pce, bits, 4); // valid_cc_element_tag_select + } + + mpeg4_bits_aligment(bits, 8); // byte_alignment(); + mpeg4_bits_aligment(pce, 8); + + comment_field_bytes = mpeg4_bits_copy(pce, bits, 8); + for (i = 0; i < comment_field_bytes; i++) + mpeg4_bits_copy(pce, bits, 8); // comment_field_data + + assert(aac->sampling_frequency_index == sampling_frequency_index); + assert(aac->profile == object_type + 1); + return (int)((pce->bits + 7) / 8); +} + +// 4.4.1 Decoder configuration (GASpecificConfig) (p487) +/* +GASpecificConfig (samplingFrequencyIndex, channelConfiguration, audioObjectType) +{ + frameLengthFlag; 1 bslbf + dependsOnCoreCoder; 1 bslbf + if (dependsOnCoreCoder) { + coreCoderDelay; 14 uimsbf + } + extensionFlag; 1 bslbf + if (! channelConfiguration) { + program_config_element (); + } + if ((audioObjectType == 6) || (audioObjectType == 20)) { + layerNr; 3 uimsbf + } + if (extensionFlag) { + if (audioObjectType == 22) { + numOfSubFrame; 5 bslbf + layer_length; 11 bslbf + } + if (audioObjectType == 17 || audioObjectType == 19 || audioObjectType == 20 || audioObjectType == 23) { + aacSectionDataResilienceFlag; 1 bslbf + aacScalefactorDataResilienceFlag; 1 bslbf + aacSpectralDataResilienceFlag; 1 bslbf + } + extensionFlag3; 1 bslbf + if (extensionFlag3) { + // tbd in version 3 + } + } +} +*/ +static int mpeg4_aac_ga_specific_config_load(struct mpeg4_bits_t* bits, struct mpeg4_aac_t* aac) +{ + int extensionFlag; + struct mpeg4_bits_t pce; + + mpeg4_bits_read(bits); // frameLengthFlag + if (mpeg4_bits_read(bits)) // dependsOnCoreCoder + mpeg4_bits_read_uint16(bits, 14); // coreCoderDelay + extensionFlag = mpeg4_bits_read(bits); // extensionFlag + + if (0 == aac->channel_configuration) + { + mpeg4_bits_init(&pce, aac->pce, sizeof(aac->pce)); + aac->npce = mpeg4_aac_pce_load(bits, aac, &pce); // update channel count + } + + if (6 == aac->profile || 20 == aac->profile) + mpeg4_bits_read_uint8(bits, 3); // layerNr + + if (extensionFlag) + { + if (22 == aac->profile) + { + mpeg4_bits_read_uint8(bits, 5); // numOfSubFrame + mpeg4_bits_read_uint16(bits, 11); // layer_length + } + + if (17 == aac->profile || 19 == aac->profile || 20 == aac->profile || 23 == aac->profile) + { + mpeg4_bits_read(bits); // aacSectionDataResilienceFlag + mpeg4_bits_read(bits); // aacScalefactorDataResilienceFlag + mpeg4_bits_read(bits); // aacSpectralDataResilienceFlag + } + + if (mpeg4_bits_read(bits)) // extensionFlag3 + { + // tbd in version 3 + assert(0); + } + } + + return mpeg4_bits_error(bits); +} + +static int mpeg4_aac_celp_specific_config_load(struct mpeg4_bits_t* bits, struct mpeg4_aac_t* aac) +{ + int ExcitationMode; + if (mpeg4_bits_read(bits)) // isBaseLayer + { + // CelpHeader + + ExcitationMode = mpeg4_bits_read(bits); + mpeg4_bits_read(bits); // SampleRateMode + mpeg4_bits_read(bits); // FineRateControl + + // Table 3.50 - Description of ExcitationMode + if (ExcitationMode == 1 /*RPE*/) + { + mpeg4_bits_read_n(bits, 3); // RPE_Configuration + } + if (ExcitationMode == 0 /*MPE*/) + { + mpeg4_bits_read_n(bits, 5); // MPE_Configuration + mpeg4_bits_read_n(bits, 2); // NumEnhLayers + mpeg4_bits_read(bits); // BandwidthScalabilityMode + } + } + else + { + if (mpeg4_bits_read(bits)) // isBWSLayer + mpeg4_bits_read_n(bits, 2); // BWS_configuration + else + mpeg4_bits_read_n(bits, 2); // CELP-BRS-id + } + + (void)aac; + return mpeg4_bits_error(bits); +} + +// ISO/IEC 23003-1 Table 9.1 ¡ª Syntax of SpatialSpecificConfig() +/* +SpatialSpecificConfig() +{ + bsSamplingFrequencyIndex; 4 uimsbf + if ( bsSamplingFrequencyIndex == 0xf ) { + bsSamplingFrequency; 24 uimsbf + } + bsFrameLength; 7 uimsbf + bsFreqRes; 3 uimsbf + bsTreeConfig; 4 uimsbf + if (bsTreeConfig == ¡®0111¡¯) { + bsNumInCh; 4 uimsbf + bsNumLFE 2 uimsbf + bsHasSpeakerConfig 1 uimsbf + if ( bsHasSpeakerConfig == 1 ) { + audioChannelLayout = SpeakerConfig3d(); Note 1 + } + } + + bsQuantMode; 2 uimsbf + bsOneIcc; 1 uimsbf + bsArbitraryDownmix; 1 uimsbf + bsFixedGainSur; 3 uimsbf + bsFixedGainLFE; 3 uimsbf + bsFixedGainDMX; 3 uimsbf + bsMatrixMode; 1 uimsbf + bsTempShapeConfig; 2 uimsbf + bsDecorrConfig; 2 uimsbf + bs3DaudioMode; 1 uimsbf + + if ( bsTreeConfig == ¡®0111¡¯ ) { + for (i=0; i< NumInCh - NumLfe; i++) { + defaultCld[i] = 1; + ottModelfe[i] = 0; + } + for (i= NumInCh - NumLfe; i< NumInCh; i++) { + defaultCld[i] = 1; + ottModelfe[i] = 1; + } + } + + for (i=0; ibits; + aac->profile = mpeg4_aac_get_audio_object_type(bits); + aac->sampling_frequency_index = mpeg4_aac_get_sampling_frequency(bits); + aac->channel_configuration = mpeg4_bits_read_uint8(bits, 4); + aac->channels = mpeg4_aac_channel_count(aac->channel_configuration); + aac->sampling_frequency = mpeg4_aac_audio_frequency_to(aac->sampling_frequency_index); + aac->extension_frequency = aac->sampling_frequency; + aac->extension_channel_configuration = aac->channel_configuration; + + if (5 == aac->profile || 29 == aac->profile) + { + aac->extension_audio_object_type = 5; + aac->sbr = 1; + if (29 == aac->profile) + aac->ps = 1; + extensionSamplingFrequencyIndex = mpeg4_aac_get_sampling_frequency(bits); + aac->extension_frequency = mpeg4_aac_audio_frequency_to(extensionSamplingFrequencyIndex); + aac->profile = mpeg4_aac_get_audio_object_type(bits); + if (22 == aac->profile) + aac->extension_channel_configuration = mpeg4_bits_read_uint8(bits, 4); + } + else + { + aac->extension_audio_object_type = 0; + } + + switch (aac->profile) + { + case 1: case 2: case 3: case 4: case 6: case 7: + case 17: case 19: case 20: case 21: case 22: case 23: + mpeg4_aac_ga_specific_config_load(bits, aac); + break; + + case 8: + mpeg4_aac_celp_specific_config_load(bits, aac); + break; + + case 30: + /*sacPayloadEmbedding=*/ mpeg4_bits_read(bits); + break; + + default: + assert(0); + return bits->bits - offset; + } + + switch (aac->profile) + { + case 17: case 19: case 20: case 21: case 22: + case 23: case 24: case 25: case 26: case 27: case 39: + epConfig = mpeg4_bits_read_uint8(bits, 2); + if (2 == epConfig || 3 == epConfig) + { + // 1.8.2.1 Error protection specific configuration (p96) + // TODO: ErrorProtectionSpecificConfig(); + assert(0); + } + if (3 == epConfig) + { + if (mpeg4_bits_read(bits)) // directMapping + { + // tbd + assert(0); + } + } + break; + + default: + break; // do nothing; + } + + if (5 != aac->extension_audio_object_type && mpeg4_bits_remain(bits) >= 16) + { + syncExtensionType = mpeg4_bits_read_uint16(bits, 11); + if (0x2b7 == syncExtensionType) + { + aac->extension_audio_object_type = mpeg4_aac_get_audio_object_type(bits); + if (5 == aac->extension_audio_object_type) + { + aac->sbr = mpeg4_bits_read(bits); + if (aac->sbr) + { + extensionSamplingFrequencyIndex = mpeg4_aac_get_sampling_frequency(bits); + aac->extension_frequency = mpeg4_aac_audio_frequency_to(extensionSamplingFrequencyIndex); + if (mpeg4_bits_remain(bits) >= 12) + { + syncExtensionType = mpeg4_bits_read_uint16(bits, 11); + if (0x548 == syncExtensionType) + aac->ps = mpeg4_bits_read(bits); + } + } + } + if (22 == aac->extension_audio_object_type) + { + aac->sbr = mpeg4_bits_read(bits); + if (aac->sbr) + { + extensionSamplingFrequencyIndex = mpeg4_aac_get_sampling_frequency(bits); + aac->extension_frequency = mpeg4_aac_audio_frequency_to(extensionSamplingFrequencyIndex); + } + aac->extension_channel_configuration = mpeg4_bits_read_uint8(bits, 4); + } + } + } + + return bits->bits - offset; +} + +int mpeg4_aac_audio_specific_config_load2(const uint8_t* data, size_t bytes, struct mpeg4_aac_t* aac) +{ + struct mpeg4_bits_t bits; + mpeg4_bits_init(&bits, (void*)data, bytes); + mpeg4_aac_audio_specific_config_load3(&bits, aac); + mpeg4_bits_aligment(&bits, 8); + return mpeg4_bits_error(&bits) ? -1 : (int)(bits.bits / 8); +} + +int mpeg4_aac_audio_specific_config_save2(const struct mpeg4_aac_t* aac, uint8_t* data, size_t bytes) +{ + if (bytes < 2 + (size_t)aac->npce) + return -1; + + memcpy(data + 2, aac->pce, aac->npce); + return 2 + aac->npce; + //data[2 + aac->npce] = 0x56; + //data[2 + aac->npce + 1] = 0xe5; + //data[2 + aac->npce + 2] = 0x00; + //return 2 + aac->npce + 3; +} + +int mpeg4_aac_adts_pce_load(const uint8_t* data, size_t bytes, struct mpeg4_aac_t* aac) +{ + int i; + size_t offset = 7; + struct mpeg4_bits_t bits, pce; + + if (0 == (data[1] & 0x01)) // protection_absent + { + // number_of_raw_data_blocks_in_frame + for (i = 1; i <= (data[6] & 0x03); i++) + offset += 2; // raw_data_block_position 16-bits + offset += 2; // crc_check 16-bits + } + + if (bytes <= offset) + return (int)offset; + + mpeg4_bits_init(&bits, (uint8_t*)data + offset, bytes - offset); + if (ID_PCE == mpeg4_bits_read_uint8(&bits, 3)) + { + mpeg4_bits_init(&pce, aac->pce, sizeof(aac->pce)); + aac->npce = mpeg4_aac_pce_load(&bits, aac, &pce); + return mpeg4_bits_error(&bits) ? -1 : (int)(7 + (pce.bits + 7) / 8); + } + return 7; +} + +int mpeg4_aac_adts_pce_save(uint8_t* data, size_t bytes, const struct mpeg4_aac_t* aac) +{ + struct mpeg4_aac_t src; + struct mpeg4_bits_t pce, adts; + if ((size_t)aac->npce + 7 > bytes) + return 0; + memcpy(&src, aac, sizeof(src)); +// assert(data[1] & 0x01); // disable protection_absent + mpeg4_bits_init(&pce, (uint8_t*)aac->pce, aac->npce); + mpeg4_bits_init(&adts, (uint8_t*)data + 7, bytes - 7); + mpeg4_bits_write_uint8(&adts, ID_PCE, 3); + mpeg4_aac_pce_load(&pce, &src, &adts); + assert(src.channels == aac->channels && (adts.bits + 7) / 8 <= bytes); + return mpeg4_bits_error(&pce) ? 0 : (int)((adts.bits+7) / 8); +} + +static size_t mpeg4_aac_stream_mux_config_load3(struct mpeg4_bits_t* bits, struct mpeg4_aac_t* aac) +{ + uint8_t audioMuxVersion = 0; + uint8_t numSubFrames; + uint8_t numProgram; + uint8_t numLayer; + uint8_t allStreamsSameTimeFraming; + uint8_t profile = 0; + uint64_t ascLen; + size_t offset; + int streamCnt, prog, lay; + + offset = bits->bits; + audioMuxVersion = (uint8_t)mpeg4_bits_read(bits); + if (!audioMuxVersion || 0 == mpeg4_bits_read(bits)) + { + if (1 == audioMuxVersion) + /*taraBufferFullness =*/ mpeg4_bits_read_latm(bits); + + streamCnt = 0; + allStreamsSameTimeFraming = (uint8_t)mpeg4_bits_read(bits); + numSubFrames = (uint8_t)mpeg4_bits_read_n(bits, 6); + numProgram = (uint8_t)mpeg4_bits_read_n(bits, 4); + for (prog = 0; prog <= numProgram; prog++) + { + numLayer = (uint8_t)mpeg4_bits_read_n(bits, 3); + for (lay = 0; lay <= numLayer; lay++) + { + //progSIndx[streamCnt] = prog; + //laySIndx[streamCnt] = lay; + //streamID[prog][lay] = streamCnt++; + if ( (prog == 0 && lay == 0) || 0 == (uint8_t)mpeg4_bits_read(bits)) + { + profile = aac->profile; // previous profile + if (audioMuxVersion == 0) { + mpeg4_aac_audio_specific_config_load3(bits, aac); + } else { + ascLen = mpeg4_bits_read_latm(bits); + ascLen -= mpeg4_aac_audio_specific_config_load3(bits, aac); + mpeg4_bits_skip(bits, (size_t)ascLen); + } + } + + //frameLengthType[streamID[prog][lay]] = (uint8_t)mpeg4_bits_read_n(bits, 3); + //switch (frameLengthType[streamID[prog][lay]]) + switch (mpeg4_bits_read_n(bits, 3)) + { + case 0: + /*latmBufferFullness[streamID[prog][lay]] =*/ (uint8_t)mpeg4_bits_read_n(bits, 8); + if (!allStreamsSameTimeFraming) + { + // fixme + //if ((AudioObjectType[lay] == 6 || AudioObjectType[lay] == 20) && + // (AudioObjectType[lay - 1] == 8 || AudioObjectType[lay - 1] == 24)) + if( (aac->profile == 6 || aac->profile == 20) && (profile == 8 || profile == 24) ) + { + /*coreFrameOffset =*/ (uint8_t)mpeg4_bits_read_n(bits, 6); + } + } + break; + + case 1: + /*frameLength[streamID[prog][lay]] =*/ (uint16_t)mpeg4_bits_read_n(bits, 9); + break; + + case 3: + case 4: + case 5: + /*CELPframeLengthTableIndex[streamID[prog][lay]] =*/ (uint16_t)mpeg4_bits_read_n(bits, 6); + break; + + case 6: + case 7: + /*HVXCframeLengthTableIndex[streamID[prog][lay]] =*/ (uint16_t)mpeg4_bits_read_n(bits, 1); + break; + + default: + // nothing to do + break; + } + } + } + + // otherDataPresent + if (mpeg4_bits_read(bits)) + { + if (audioMuxVersion == 1) + { + /*otherDataLenBits =*/ mpeg4_bits_read_latm(bits); + } + else + { + /*otherDataLenBits =*/ mpeg4_bits_read_n(bits, 8); /* helper variable 32bit */ + while(mpeg4_bits_read(bits)) + { + /*otherDataLenBits <<= 8; + otherDataLenBits +=*/ mpeg4_bits_read_n(bits, 8); + } + } + } + + // crcCheckPresent + if (mpeg4_bits_read(bits)) + /*crcCheckSum =*/ mpeg4_bits_read_n(bits, 8); + } + else + { + /*tbd*/ + } + + return bits->bits - offset; +} + +int mpeg4_aac_stream_mux_config_load2(const uint8_t* data, size_t bytes, struct mpeg4_aac_t* aac) +{ + struct mpeg4_bits_t bits; + mpeg4_bits_init(&bits, (void*)data, bytes); + mpeg4_aac_stream_mux_config_load3(&bits, aac); + mpeg4_bits_aligment(&bits, 8); + return mpeg4_bits_error(&bits) ? -1 : (int)(bits.bits / 8); +} diff --git a/MediaServer/libflv/source/mpeg4-aac.c b/MediaServer/libflv/source/mpeg4-aac.c new file mode 100644 index 0000000..1fff46a --- /dev/null +++ b/MediaServer/libflv/source/mpeg4-aac.c @@ -0,0 +1,364 @@ +#include "mpeg4-aac.h" +#include +#include +#include + +int mpeg4_aac_adts_pce_load(const uint8_t* data, size_t bytes, struct mpeg4_aac_t* aac); +int mpeg4_aac_adts_pce_save(uint8_t* data, size_t bytes, const struct mpeg4_aac_t* aac); +int mpeg4_aac_audio_specific_config_load2(const uint8_t* data, size_t bytes, struct mpeg4_aac_t* aac); +int mpeg4_aac_audio_specific_config_save2(const struct mpeg4_aac_t* aac, uint8_t* data, size_t bytes); +int mpeg4_aac_stream_mux_config_load2(const uint8_t* data, size_t bytes, struct mpeg4_aac_t* aac); + +/* +// ISO-14496-3 adts_frame (p122) + +adts_fixed_header() +{ + syncword; 12 bslbf + ID; 1 bslbf + layer; 2 uimsbf + protection_absent; 1 bslbf + profile_ObjectType; 2 uimsbf + sampling_frequency_index; 4 uimsbf + private_bit; 1 bslbf + channel_configuration; 3 uimsbf + original_copy; 1 bslbf + home; 1 bslbf +} + +adts_variable_header() +{ + copyright_identification_bit; 1 bslbf + copyright_identification_start; 1 bslbf + aac_frame_length; 13 bslbf + adts_buffer_fullness; 11 bslbf + number_of_raw_data_blocks_in_frame; 2 uimsbf +} +*/ +/// @return >=0-adts header length, <0-error +int mpeg4_aac_adts_load(const uint8_t* data, size_t bytes, struct mpeg4_aac_t* aac) +{ + if (bytes < 7) return -1; + + memset(aac, 0, sizeof(struct mpeg4_aac_t)); + assert(0xFF == data[0] && 0xF0 == (data[1] & 0xF0)); /* syncword */ + aac->profile = ((data[2] >> 6) & 0x03) + 1; // 2 bits: the MPEG-2 Audio Object Type add 1 + aac->sampling_frequency_index = (data[2] >> 2) & 0x0F; // 4 bits: MPEG-4 Sampling Frequency Index (15 is forbidden) + aac->channel_configuration = ((data[2] & 0x01) << 2) | ((data[3] >> 6) & 0x03); // 3 bits: MPEG-4 Channel Configuration + assert(aac->profile > 0 && aac->profile < 31); + assert(aac->channel_configuration >= 0 && aac->channel_configuration <= 7); + assert(aac->sampling_frequency_index >= 0 && aac->sampling_frequency_index <= 0xc); + aac->channels = mpeg4_aac_channel_count(aac->channel_configuration); + aac->sampling_frequency = mpeg4_aac_audio_frequency_to(aac->sampling_frequency_index); + aac->extension_frequency = aac->sampling_frequency; + + if (0 == aac->channel_configuration) + return mpeg4_aac_adts_pce_load(data, bytes, aac); + return 7; +} + +/// @return >=0-adts header length, <0-error +int mpeg4_aac_adts_save(const struct mpeg4_aac_t* aac, size_t payload, uint8_t* data, size_t bytes) +{ + const uint8_t ID = 0; // 0-MPEG4/1-MPEG2 + size_t len = payload + 7; + if (bytes < 7 || len >= (1 << 12)) return -1; + + if (0 == aac->channel_configuration && aac->npce > 0) + len += mpeg4_aac_adts_pce_save(data, bytes, aac); + + assert(aac->profile > 0 && aac->profile < 31); + assert(aac->channel_configuration >= 0 && aac->channel_configuration <= 7); + assert(aac->sampling_frequency_index >= 0 && aac->sampling_frequency_index <= 0xc); + data[0] = 0xFF; /* 12-syncword */ + data[1] = 0xF0 /* 12-syncword */ | (ID << 3)/*1-ID*/ | (0x00 << 2) /*2-layer*/ | 0x01 /*1-protection_absent*/; + data[2] = ((aac->profile - 1) << 6) | ((aac->sampling_frequency_index & 0x0F) << 2) | ((aac->channel_configuration >> 2) & 0x01); + data[3] = ((aac->channel_configuration & 0x03) << 6) | ((len >> 11) & 0x03); /*0-original_copy*/ /*0-home*/ /*0-copyright_identification_bit*/ /*0-copyright_identification_start*/ + data[4] = (uint8_t)(len >> 3); + data[5] = ((len & 0x07) << 5) | 0x1F; + data[6] = 0xFC /*| ((len / (1024 * aac->channels)) & 0x03)*/; + return (int)(len - payload); +} + +int mpeg4_aac_adts_frame_length(const uint8_t* data, size_t bytes) +{ + uint16_t len; + if (bytes < 7) return -1; + assert(0xFF == data[0] && 0xF0 == (data[1] & 0xF0)); /* syncword */ + len = ((uint16_t)(data[3] & 0x03) << 11) | ((uint16_t)data[4] << 3) | ((uint16_t)(data[5] >> 5) & 0x07); + return len; +} + +// ISO-14496-3 AudioSpecificConfig (p52) +/* +audioObjectType; 5 uimsbf +if (audioObjectType == 31) { + audioObjectType = 32 + audioObjectTypeExt; 6 uimsbf +} +samplingFrequencyIndex; 4 bslbf +if ( samplingFrequencyIndex == 0xf ) { + samplingFrequency; 24 uimsbf +} +channelConfiguration; 4 bslbf +*/ +/// @return >=0-adts header length, <0-error +int mpeg4_aac_audio_specific_config_load(const uint8_t* data, size_t bytes, struct mpeg4_aac_t* aac) +{ + if (bytes < 2) return -1; + + memset(aac, 0, sizeof(struct mpeg4_aac_t)); + aac->profile = (data[0] >> 3) & 0x1F; + aac->sampling_frequency_index = ((data[0] & 0x7) << 1) | ((data[1] >> 7) & 0x01); + aac->channel_configuration = (data[1] >> 3) & 0x0F; + assert(aac->profile > 0 && aac->profile < 31); + assert(aac->channel_configuration >= 0 && aac->channel_configuration <= 7); + assert(aac->sampling_frequency_index >= 0 && aac->sampling_frequency_index <= 0xc); + aac->channels = mpeg4_aac_channel_count(aac->channel_configuration); + aac->sampling_frequency = mpeg4_aac_audio_frequency_to(aac->sampling_frequency_index); + aac->extension_frequency = aac->sampling_frequency; + + if (bytes > 2) + return mpeg4_aac_audio_specific_config_load2(data, bytes, aac); + return 2; +} + +// ISO-14496-3 AudioSpecificConfig +int mpeg4_aac_audio_specific_config_save(const struct mpeg4_aac_t* aac, uint8_t* data, size_t bytes) +{ + uint8_t channel_configuration; + if (bytes < 2+ (size_t)aac->npce) return -1; + + channel_configuration = aac->npce > 0 ? 0 : aac->channel_configuration; + assert(aac->profile > 0 && aac->profile < 31); + assert(aac->channel_configuration >= 0 && aac->channel_configuration <= 7); + assert(aac->sampling_frequency_index >= 0 && aac->sampling_frequency_index <= 0xc); + data[0] = (aac->profile << 3) | ((aac->sampling_frequency_index >> 1) & 0x07); + data[1] = ((aac->sampling_frequency_index & 0x01) << 7) | ((channel_configuration & 0xF) << 3) | (0 << 2) /* frame length-1024 samples*/ | (0 << 1) /* don't depend on core */ | 0 /* not extension */; + + if (0 == aac->channel_configuration && aac->npce > 0) + return mpeg4_aac_audio_specific_config_save2(aac, data, bytes); + return 2; +} + +// ISO/IEC 14496-3:2009(E) Table 1.42 - Syntax of StreamMuxConfig() (p83) +int mpeg4_aac_stream_mux_config_load(const uint8_t* data, size_t bytes, struct mpeg4_aac_t* aac) +{ + if (bytes < 6) return -1; + + memset(aac, 0, sizeof(*aac)); + if (6 == bytes && 0x40 == data[0] && 0 == (data[1] & 0xFE)) + { + // fast path + // [0] 0-audioMuxVersion(1), 1-allStreamsSameTimeFraming(1), 0-numSubFrames(6) + assert(0 == (0x80 & data[0])); // audioMuxVersion: 0 + aac->profile = ((data[1] & 0x01) << 4) | (data[2] >> 4); // 0-numProgram(4), 0-numLayer(3), 1-ASC(1) + aac->sampling_frequency_index = data[2] & 0x0F; + aac->channel_configuration = data[3] >> 4; + assert(aac->profile > 0 && aac->profile < 31); + assert(aac->channel_configuration >= 0 && aac->channel_configuration <= 7); + assert(aac->sampling_frequency_index >= 0 && aac->sampling_frequency_index <= 0xc); + aac->channels = mpeg4_aac_channel_count(aac->channel_configuration); + aac->sampling_frequency = mpeg4_aac_audio_frequency_to(aac->sampling_frequency_index); + aac->extension_frequency = aac->sampling_frequency; + return 6; + } + + return mpeg4_aac_stream_mux_config_load2(data, bytes, aac); +} + +// ISO/IEC 14496-3:2009(E) Table 1.42 - Syntax of StreamMuxConfig() (p83) +int mpeg4_aac_stream_mux_config_save(const struct mpeg4_aac_t* aac, uint8_t* data, size_t bytes) +{ + int profile; + int frequncy; + if (bytes < 6) return -1; + + profile = aac->sbr ? aac->extension_audio_object_type : aac->profile; + frequncy = mpeg4_aac_audio_frequency_from(aac->extension_frequency); + frequncy = (aac->sbr || aac->ps) && -1 != frequncy ? frequncy : 0; + + assert(aac->profile > 0 && aac->profile < 31); + assert(aac->channel_configuration >= 0 && aac->channel_configuration <= 7); + assert(aac->sampling_frequency_index >= 0 && aac->sampling_frequency_index <= 0xc); + data[0] = 0x40; // 0-audioMuxVersion(1), 1-allStreamsSameTimeFraming(1), 0-numSubFrames(6) + data[1] = 0x00 | ((profile >> 4) & 0x01); // 0-numProgram(4), 0-numLayer(3) + data[2] = ((profile & 0x0F) << 4) | (aac->sampling_frequency_index & 0x0F); + data[3] = ((aac->channel_configuration & 0x0F) << 4) | (-1 != frequncy ? (frequncy & 0x0F) : 0); // 0-GASpecificConfig(3), 0-frameLengthType(1) + data[4] = 0x3F; // 0-frameLengthType(2), 111111-latmBufferFullness(6) + data[5] = 0xC0; // 11-latmBufferFullness(2), 0-otherDataPresent, 0-crcCheckPresent + return 6; +} + +int mpeg4_aac_codecs(const struct mpeg4_aac_t* aac, char* codecs, size_t bytes) +{ + // https://tools.ietf.org/html/rfc6381#section-3.4 + return snprintf(codecs, bytes, "mp4a.40.%u", (unsigned int)aac->profile); +} + +// Table 1.6 ¨C Levels for the High Quality Audio Profile +static int mpeg4_aac_high_quality_level(const struct mpeg4_aac_t* aac) +{ + if (aac->sampling_frequency <= 22050) + { + if (aac->channel_configuration <= 2) + return 1; // Level 1/5 + } + else if (aac->sampling_frequency <= 48000) + { + if (aac->channel_configuration <= 2) + return 2; // Level 2/6 + else if (aac->channel_configuration <= 5) + return 3; // Level 3/4/7/8 + } + + return 8; +} + +// Table 1.10 ¨C Levels for the AAC Profile +static int mpeg4_aac_level(const struct mpeg4_aac_t* aac) +{ + if (aac->sampling_frequency <= 24000) + { + if (aac->channel_configuration <= 2) + return 1; // AAC Profile, Level 1 + } + else if (aac->sampling_frequency <= 48000) + { + if (aac->channel_configuration <= 2) + return 2; // Level 2 + else if (aac->channel_configuration <= 5) + return 4; // Level 4 + } + else if (aac->sampling_frequency <= 96000) + { + if (aac->channel_configuration <= 5) + return 5; // Level 5 + } + + return 5; +} + +static int mpeg4_aac_he_level(const struct mpeg4_aac_t* aac) +{ + if (aac->sampling_frequency <= 48000) + { + if (aac->channel_configuration <= 2) + return aac->sbr ? 3 : 2; // Level 2/3 + else if (aac->channel_configuration <= 5) + return 4; // Level 4 + } + else if (aac->sampling_frequency <= 96000) + { + if (aac->channel_configuration <= 5) + return 5; // Level 5 + } + + return 5; +} + +// ISO/IEC 14496-3:2009(E) Table 1.14 - audioProfileLevelIndication values (p51) +int mpeg4_aac_profile_level(const struct mpeg4_aac_t* aac) +{ + // Table 1.10 - Levels for the AAC Profile (p49) + // Table 1.14 - audioProfileLevelIndication values (p51) + switch (aac->profile) + { + case MPEG4_AAC_LC: + return mpeg4_aac_level(aac) - 1 + 0x28; // AAC Profile + case MPEG4_AAC_SBR: + return mpeg4_aac_he_level(aac) - 2 + 0x2C; // High Efficiency AAC Profile + case MPEG4_AAC_PS: + return mpeg4_aac_he_level(aac) - 2 + 0x30; // High Efficiency AAC v2 Profile + case MPEG4_AAC_CELP: + return mpeg4_aac_high_quality_level(aac) - 1 + 0x0E; // High Quality Audio Profile + default: + return 1; // Main Audio Profile, Level 1 + } +} + +#define ARRAYOF(arr) sizeof(arr)/sizeof(arr[0]) + +static const int s_frequency[] = { 96000, 88200, 64000, 48000, 44100, 32000, 24000, 22050, 16000, 12000, 11025, 8000, 7350 }; + +int mpeg4_aac_audio_frequency_to(enum mpeg4_aac_frequency index) +{ + if (index < 0 || index >= ARRAYOF(s_frequency)) + return 0; + return s_frequency[index]; +} + +int mpeg4_aac_audio_frequency_from(int frequence) +{ + int i = 0; + while (i < ARRAYOF(s_frequency) && s_frequency[i] != frequence) i++; + return i >= ARRAYOF(s_frequency) ? -1 : i; +} + +uint8_t mpeg4_aac_channel_count(uint8_t channel_configuration) +{ + static const uint8_t s_channels[] = { 0, 1, 2, 3, 4, 5, 6, 8 }; + if (channel_configuration < 0 || channel_configuration >= ARRAYOF(s_channels)) + return 0; + return s_channels[channel_configuration]; +} +#undef ARRAYOF + +#if defined(_DEBUG) || defined(DEBUG) +void mpeg4_aac_test(void) +{ + struct mpeg4_aac_t aac, aac2; + const unsigned char asc[] = { 0x13, 0x88 }; + const unsigned char adts[] = { 0xFF, 0xF1, 0x5C, 0x40, 0x01, 0x1F, 0xFC }; +// const unsigned char ascsbr[] = { 0x13, 0x10, 0x56, 0xe5, 0x9d, 0x48, 0x00 }; + const unsigned char ascsbr[] = { 0x2b, 0x92, 0x08, 0x00 }; + const unsigned char asc8ch[] = { 0x12, 0x00, 0x05, 0x08, 0x48, 0x00, 0x20, 0x00, 0xC6, 0x40, 0x0D, 0x4C, 0x61, 0x76, 0x63, 0x35, 0x38, 0x2E, 0x39, 0x37, 0x2E, 0x31, 0x30, 0x32, 0x56, 0xE5, 0x00 }; + // https://datatracker.ietf.org/doc/html/rfc6416#page-25 + const unsigned char mux1[] = { 0x40, 0x00, 0x8B, 0x18, 0x38, 0x83, 0x80 }; // 6 kbit/s CELP + const unsigned char mux2[] = { 0x40, 0x00, 0x26, 0x20, 0x3f, 0xc0 }; // 64 kbit/s AAC LC Stereo + const unsigned char mux3[] = { 0x40, 0x00, 0x56, 0x23, 0x10, 0x1f, 0xe0 }; // Hierarchical Signaling of SBR + const unsigned char mux4[] = { 0x40, 0x00, 0x26, 0x10, 0x3f, 0xc0 }; // HE AAC v2 Signaling + const unsigned char mux5[] = { 0x40, 0x01, 0xd6, 0x13, 0x10, 0x1f, 0xe0 }; // Hierarchical Signaling of PS + const unsigned char mux6[] = { 0x8F, 0xF8, 0x00, 0x41, 0x92, 0xB1, 0x18, 0x80, 0xFF, 0x0D, 0xDE, 0x36, 0x99, 0xF2, 0x40, 0x8C, 0x00, 0x53, 0x6C, 0x02, 0x31, 0x3C, 0xF3, 0xCE, 0x0F, 0xF0 }; // MPEG Surround + const unsigned char mux7[] = { 0x40, 0x00, 0x56, 0x23, 0x10, 0x1f, 0xe0 }; // MPEG Surround with Extended SDP Parameters + const unsigned char mux8[] = { 0x8F, 0xF8, 0x00, 0x06, 0x52, 0xB9, 0x20, 0x87, 0x6A, 0x83, 0xA1, 0xF4, 0x40, 0x88, 0x40, 0x53, 0x62, 0x0F, 0xF0 }; // MPEG Surround with Single-Layer Configuration + + unsigned char data[32]; + + assert(sizeof(ascsbr) == mpeg4_aac_audio_specific_config_load(ascsbr, sizeof(ascsbr), &aac)); + assert(2 == aac.profile && 7 == aac.sampling_frequency_index && 2 == aac.channel_configuration); + //assert(sizeof(ascsbr) == mpeg4_aac_audio_specific_config_save(&aac, data, sizeof(data))); + //assert(0 == memcmp(ascsbr, data, sizeof(ascsbr))); + + assert(sizeof(asc) == mpeg4_aac_audio_specific_config_load(asc, sizeof(asc), &aac)); + assert(2 == aac.profile && 7 == aac.sampling_frequency_index && 1 == aac.channel_configuration); + assert(sizeof(asc) == mpeg4_aac_audio_specific_config_save(&aac, data, sizeof(data))); + assert(0 == memcmp(asc, data, sizeof(asc))); + + assert(sizeof(adts) == mpeg4_aac_adts_save(&aac, 1, data, sizeof(data))); + assert(0 == memcmp(adts, data, sizeof(adts))); + assert(7 == mpeg4_aac_adts_load(data, sizeof(adts), &aac2)); + assert(0 == memcmp(&aac, &aac2, sizeof(aac))); + + assert(22050 == mpeg4_aac_audio_frequency_to(aac.sampling_frequency_index)); + assert(aac.sampling_frequency_index == mpeg4_aac_audio_frequency_from(22050)); + + //assert(sizeof(ascsbr) == mpeg4_aac_audio_specific_config_load(ascsbr, sizeof(ascsbr), &aac)); + //assert(2 == aac.profile && 6 == aac.sampling_frequency_index && 1 == aac.channel_configuration); + + assert(sizeof(asc8ch) == mpeg4_aac_audio_specific_config_load(asc8ch, sizeof(asc8ch), &aac)); + assert(2 == aac.profile && 4 == aac.sampling_frequency_index && 8 == aac.channels); + assert(29 == mpeg4_aac_adts_save(&aac, 1, data, sizeof(data))); + + memset(&aac, 0, sizeof(aac)); + mpeg4_aac_stream_mux_config_load(mux1, sizeof(mux1), &aac); + mpeg4_aac_stream_mux_config_load(mux2, sizeof(mux2), &aac); + mpeg4_aac_stream_mux_config_load(mux3, sizeof(mux3), &aac); + mpeg4_aac_stream_mux_config_load(mux4, sizeof(mux4), &aac); + mpeg4_aac_stream_mux_config_load(mux5, sizeof(mux5), &aac); + //mpeg4_aac_stream_mux_config_load(mux6, sizeof(mux6), &aac); + //mpeg4_aac_stream_mux_config_load(mux7, sizeof(mux7), &aac); + //mpeg4_aac_stream_mux_config_load(mux8, sizeof(mux8), &aac); + mpeg4_aac_stream_mux_config_save(&aac, data, sizeof(data)); + //assert(0 == memcmp(data, mux1, sizeof(mux1))); +} +#endif diff --git a/MediaServer/libflv/source/mpeg4-annexbtomp4.c b/MediaServer/libflv/source/mpeg4-annexbtomp4.c new file mode 100644 index 0000000..ced21fc --- /dev/null +++ b/MediaServer/libflv/source/mpeg4-annexbtomp4.c @@ -0,0 +1,518 @@ +// ISO/IEC 14496-1:2010(E) +// Annex I: Usage of ITU-T Recommendation H.264 | ISO/IEC 14496-10 AVC (p150) +// +// 1. Start Codes shall not be present in the stream. The field indicating the size of each following NAL unit +// shall be added before NAL unit.The size of this field is defined in DecoderSpecificInfo. +// 2. It is recommended encapsulating one NAL unit in one SL packet when it is delivered over lossy environment. + +#include "mpeg4-avc.h" +#include +#include +#include +#include +#include + +#define H264_NAL_IDR 5 // Coded slice of an IDR picture +#define H264_NAL_SPS 7 // Sequence parameter set +#define H264_NAL_PPS 8 // Picture parameter set +#define H264_NAL_AUD 9 // Access unit delimiter + +#define H2645_BITSTREAM_FORMAT_DETECT + +struct h264_annexbtomp4_handle_t +{ + struct mpeg4_avc_t* avc; + int errcode; + int* update; // avc sps/pps update flags + int* vcl; + + uint8_t* out; + size_t bytes; + size_t capacity; +}; + +static const uint8_t* h264_startcode(const uint8_t *data, size_t bytes) +{ + size_t i; + for (i = 2; i + 1 < bytes; i++) + { + if (0x01 == data[i] && 0x00 == data[i - 1] && 0x00 == data[i - 2]) + return data + i + 1; + } + + return NULL; +} + +/// @return >0-ok, <=0-error +static inline int h264_avcc_length(const uint8_t* h264, size_t bytes, size_t avcc) +{ + size_t i; + uint32_t n; + + n = 0; + assert(3 <= avcc && avcc <= 4); + for (i = 0; i < avcc && i < bytes; i++) + n = (n << 8) | h264[i]; + return avcc >= bytes ? -1 : (int)n; +} + +/// @return 1-true, 0-false +static int mpeg4_h264_avcc_bitstream_valid(const uint8_t* h264, size_t bytes, size_t avcc) +{ + size_t n; + + while(avcc + 1 < bytes) + { + n = h264_avcc_length(h264, bytes, avcc); + if (n < 0 || n + avcc > bytes) + return 0; // invalid + + h264 += n + avcc; + bytes -= n + avcc; + } + + return 0 == bytes ? 1 : 0; +} + +/// @return 0-annexb, >0-avcc, <0-error +int mpeg4_h264_bitstream_format(const uint8_t* h264, size_t bytes) +{ + uint32_t n; + if (bytes < 4) + return -1; + + n = ((uint32_t)h264[0]) << 16 | ((uint32_t)h264[1]) << 8 | ((uint32_t)h264[2]); + if (0 == n && h264[3] <= 1) + { + return 0; // annexb + } + else if(1 == n) + { + // try avcc & annexb + return mpeg4_h264_avcc_bitstream_valid(h264, bytes, 4) ? 4 : 0; + } + else + { + // try avcc 4/3 bytes + return mpeg4_h264_avcc_bitstream_valid(h264, bytes, 4) ? 4 : (mpeg4_h264_avcc_bitstream_valid(h264, bytes, 3) ? 3 : -1); + } +} + +static int mpeg4_h264_avcc_nalu(const void* h264, size_t bytes, int avcc, void (*handler)(void* param, const uint8_t* nalu, size_t bytes), void* param) +{ + uint32_t n; + const uint8_t* p, * end; + + p = (const uint8_t*)h264; + end = (const uint8_t*)h264 + bytes; + for(n = h264_avcc_length(p, (int)(end - p), avcc); p + n + avcc <= end; n = h264_avcc_length(p, (int)(end - p), avcc)) + { + assert(n > 0); + if (n > 0) + { + handler(param, p + avcc, (int)n); + } + + p += n + avcc; + } + + return 0; +} + +///@param[in] h264 H.264 byte stream format data(A set of NAL units) +int mpeg4_h264_annexb_nalu(const void* h264, size_t bytes, void (*handler)(void* param, const uint8_t* nalu, size_t bytes), void* param) +{ + ptrdiff_t n; + const uint8_t* p, *next, *end; + +#if defined(H2645_BITSTREAM_FORMAT_DETECT) + int avcc; + avcc = mpeg4_h264_bitstream_format(h264, bytes); + if (avcc > 0) + return mpeg4_h264_avcc_nalu(h264, bytes, avcc, handler, param); +#endif + + end = (const uint8_t*)h264 + bytes; + p = h264_startcode((const uint8_t*)h264, bytes); + + while (p) + { + next = h264_startcode(p, (int)(end - p)); + if (next) + { + n = next - p - 3; + } + else + { + n = end - p; + } + + while (n > 0 && 0 == p[n - 1]) n--; // filter tailing zero + + assert(n > 0); + if (n > 0) + { + handler(param, p, (int)n); + } + + p = next; + } + + return 0; +} + +uint8_t mpeg4_h264_read_ue(const uint8_t* data, size_t bytes, size_t* offset) +{ + int bit, i; + int leadingZeroBits = -1; + + for (bit = 0; !bit && *offset / 8 < bytes; ++leadingZeroBits) + { + bit = (data[*offset / 8] >> (7 - (*offset % 8))) & 0x01; + ++*offset; + } + + bit = 0; + assert(leadingZeroBits < 32); + for (i = 0; i < leadingZeroBits && *offset / 8 < bytes; i++) + { + bit = (bit << 1) | ((data[*offset / 8] >> (7 - (*offset % 8))) & 0x01); + ++*offset; + } + + return (uint8_t)((1 << leadingZeroBits) - 1 + bit); +} + +static void mpeg4_avc_remove(struct mpeg4_avc_t* avc, uint8_t* ptr, size_t bytes, const uint8_t* end) +{ + uint8_t i; + assert(ptr >= avc->data && ptr + bytes <= end && end <= avc->data + sizeof(avc->data)); + memmove(ptr, ptr + bytes, end - ptr - bytes); + + for (i = 0; i < avc->nb_sps; i++) + { + if (avc->sps[i].data > ptr) + avc->sps[i].data -= bytes; + } + + for (i = 0; i < avc->nb_pps; i++) + { + if (avc->pps[i].data > ptr) + avc->pps[i].data -= bytes; + } +} + +static int h264_sps_copy(struct mpeg4_avc_t* avc, const uint8_t* nalu, size_t bytes) +{ + size_t i; + size_t offset; + uint8_t spsid; + + if (bytes < 4 + 1) + { + assert(0); + return -1; // invalid length + } + + offset = 4 * 8; // 1-NALU + 3-profile+flags+level + spsid = mpeg4_h264_read_ue(nalu, bytes, &offset); + + for (i = 0; i < avc->nb_sps; i++) + { + offset = 4 * 8; // reset offset + if (spsid == mpeg4_h264_read_ue(avc->sps[i].data, avc->sps[i].bytes, &offset)) + { + if (bytes == avc->sps[i].bytes && 0 == memcmp(nalu, avc->sps[i].data, bytes)) + return 0; // do nothing + + if (bytes > avc->sps[i].bytes && avc->off + (bytes - avc->sps[i].bytes) > sizeof(avc->data)) + { + assert(0); + return -1; // too big + } + + mpeg4_avc_remove(avc, avc->sps[i].data, avc->sps[i].bytes, avc->data + avc->off); + avc->off -= avc->sps[i].bytes; + + avc->sps[i].data = avc->data + avc->off; + avc->sps[i].bytes = (uint16_t)bytes; + memcpy(avc->sps[i].data, nalu, bytes); + avc->off += bytes; + return 1; // set update flag + } + } + + // copy new + assert(avc->nb_sps < sizeof(avc->sps) / sizeof(avc->sps[0])); + if (avc->nb_sps >= sizeof(avc->sps) / sizeof(avc->sps[0]) + || avc->off + bytes > sizeof(avc->data)) + { + assert(0); + return -1; + } + + avc->sps[avc->nb_sps].data = avc->data + avc->off; + avc->sps[avc->nb_sps].bytes = (uint16_t)bytes; + memcpy(avc->sps[avc->nb_sps].data, nalu, bytes); + avc->off += bytes; + ++avc->nb_sps; + return 1; // set update flag +} + +static int h264_pps_copy(struct mpeg4_avc_t* avc, const uint8_t* nalu, size_t bytes) +{ + size_t i; + size_t offset; + uint8_t spsid; + uint8_t ppsid; + + if (bytes < 1 + 1) + { + assert(0); + return -1; // invalid length + } + + offset = 1 * 8; // 1-NALU + ppsid = mpeg4_h264_read_ue(nalu, bytes, &offset); + spsid = mpeg4_h264_read_ue(nalu, bytes, &offset); + + for (i = 0; i < avc->nb_pps; i++) + { + offset = 1 * 8; // reset offset + if (ppsid == mpeg4_h264_read_ue(avc->pps[i].data, avc->pps[i].bytes, &offset) && spsid == mpeg4_h264_read_ue(avc->pps[i].data, avc->pps[i].bytes, &offset)) + { + if (bytes == avc->pps[i].bytes && 0 == memcmp(nalu, avc->pps[i].data, bytes)) + return 0; // do nothing + + if (bytes > avc->pps[i].bytes && avc->off + (bytes - avc->pps[i].bytes) > sizeof(avc->data)) + { + assert(0); + return -1; // too big + } + + mpeg4_avc_remove(avc, avc->pps[i].data, avc->pps[i].bytes, avc->data + avc->off); + avc->off -= avc->pps[i].bytes; + + avc->pps[i].data = avc->data + avc->off; + avc->pps[i].bytes = (uint16_t)bytes; + memcpy(avc->pps[i].data, nalu, bytes); + avc->off += bytes; + return 1; // set update flag + } + } + + // fix openh264 sps/pps id cycle (0/0, 1/1, 2/2, ..., 31/31, 0/32, 1/33, ...) + if ((unsigned int)avc->nb_pps + 1 >= sizeof(avc->pps) / sizeof(avc->pps[0]) && avc->nb_sps > 16) + { + // replace the oldest pps + mpeg4_avc_remove(avc, avc->pps[0].data, avc->pps[0].bytes, avc->data + avc->off); + avc->off -= avc->pps[0].bytes; + + avc->pps[0].data = avc->data + avc->off; + avc->pps[0].bytes = (uint16_t)bytes; + memcpy(avc->pps[0].data, nalu, bytes); + avc->off += bytes; + return 1; // set update flag + } + + // copy new + assert((unsigned int)avc->nb_pps + 1 < sizeof(avc->pps) / sizeof(avc->pps[0])); + if ((unsigned int)avc->nb_pps + 1 >= sizeof(avc->pps) / sizeof(avc->pps[0]) + || avc->off + bytes > sizeof(avc->data)) + { + assert(0); + return -1; + } + + avc->pps[avc->nb_pps].data = avc->data + avc->off; + avc->pps[avc->nb_pps].bytes = (uint16_t)bytes; + memcpy(avc->pps[avc->nb_pps].data, nalu, bytes); + avc->off += bytes; + ++avc->nb_pps; // fixme: uint8_t overflow + return 1; // set update flag +} + +int mpeg4_avc_update(struct mpeg4_avc_t* avc, const uint8_t* nalu, size_t bytes) +{ + int r; + + switch (nalu[0] & 0x1f) + { + case H264_NAL_SPS: + r = h264_sps_copy(avc, nalu, bytes); + if (1 == r || 1 == avc->nb_sps) + { + // update profile/level + avc->profile = nalu[1]; + avc->compatibility = nalu[2]; + avc->level = nalu[3]; + } + break; + + case H264_NAL_PPS: + r = h264_pps_copy(avc, nalu, bytes); + break; + + default: + r = 0; + } + + return r; +} + +static void h264_handler(void* param, const uint8_t* nalu, size_t bytes) +{ + int r; + uint8_t nalutype; + struct h264_annexbtomp4_handle_t* mp4; + mp4 = (struct h264_annexbtomp4_handle_t*)param; + + if (bytes < 1) + { + assert(0); + return; + } + + nalutype = (nalu[0]) & 0x1f; +#if defined(H2645_FILTER_AUD) + if (H264_NAL_AUD == nalutype) + return; // ignore AUD +#endif + + r = mpeg4_avc_update(mp4->avc, nalu, bytes); + if (1 == r && mp4->update) + *mp4->update = 1; + else if (r < 0) + mp4->errcode = r; + + // IDR-1, B/P-2, other-0 + if (mp4->vcl && 1 <= nalutype && nalutype <= H264_NAL_IDR) + *mp4->vcl = nalutype == H264_NAL_IDR ? 1 : 2; + + if (mp4->capacity >= mp4->bytes + bytes + 4) + { + mp4->out[mp4->bytes + 0] = (uint8_t)((bytes >> 24) & 0xFF); + mp4->out[mp4->bytes + 1] = (uint8_t)((bytes >> 16) & 0xFF); + mp4->out[mp4->bytes + 2] = (uint8_t)((bytes >> 8) & 0xFF); + mp4->out[mp4->bytes + 3] = (uint8_t)((bytes >> 0) & 0xFF); + memmove(mp4->out + mp4->bytes + 4, nalu, bytes); + mp4->bytes += bytes + 4; + } + else + { + mp4->errcode = -E2BIG; + } +} + +int h264_annexbtomp4(struct mpeg4_avc_t* avc, const void* data, size_t bytes, void* out, size_t size, int* vcl, int* update) +{ + struct h264_annexbtomp4_handle_t h; + memset(&h, 0, sizeof(h)); + h.avc = avc; + h.vcl = vcl; + h.update = update; + h.out = (uint8_t*)out; + h.capacity = size; + if (vcl) *vcl = 0; + if (update) *update = 0; + + mpeg4_h264_annexb_nalu(data, bytes, h264_handler, &h); + avc->nalu = 4; + return 0 == h.errcode ? (int)h.bytes : 0; +} + +/// h264_is_new_access_unit H.264 new access unit(frame) +/// @return 1-new access, 0-not a new access +int h264_is_new_access_unit(const uint8_t* nalu, size_t bytes) +{ + enum { NAL_NIDR = 1, NAL_PARTITION_A = 2, NAL_IDR = 5, NAL_SEI = 6, NAL_SPS = 7, NAL_PPS = 8, NAL_AUD = 9, }; + + uint8_t nal_type; + + if(bytes < 2) + return 0; + + nal_type = nalu[0] & 0x1f; + + // 7.4.1.2.3 Order of NAL units and coded pictures and association to access units + if(NAL_AUD == nal_type || NAL_SPS == nal_type || NAL_PPS == nal_type || NAL_SEI == nal_type || (14 <= nal_type && nal_type <= 18)) + return 1; + + // 7.4.1.2.4 Detection of the first VCL NAL unit of a primary coded picture + if(NAL_NIDR == nal_type || NAL_PARTITION_A == nal_type || NAL_IDR == nal_type) + { + // Live555 H264or5VideoStreamParser::parse + // The high-order bit of the byte after the "nal_unit_header" tells us whether it's + // the start of a new 'access unit' (and thus the current NAL unit ends an 'access unit'): + return (nalu[1] & 0x80) ? 1 : 0; // first_mb_in_slice + } + + return 0; +} + +#if defined(_DEBUG) || defined(DEBUG) +static void mpeg4_h264_bitstream_format_test(void) +{ + const uint8_t bs3[] = { 0x00,0x00,0x01,0x67,0x42,0xe0,0x1e,0xab,0xcd, }; + const uint8_t bs4[] = { 0x00,0x00,0x00,0x01,0x67,0x42,0xe0,0x1e,0xab,0xcd, }; + const uint8_t bs5[] = { 0x00,0x00,0x00,0x00,0x01,0x67,0x42,0xe0,0x1e,0xab,0xcd, }; + const uint8_t avcc3[] = { 0x00,0x00,0x06,0x67,0x42,0xe0,0x1e,0xab,0xcd, }; + const uint8_t avcc4[] = { 0x00,0x00,0x00,0x06,0x67,0x42,0xe0,0x1e,0xab,0xcd, }; + assert(0 == mpeg4_h264_bitstream_format(bs3, sizeof(bs3))); + assert(0 == mpeg4_h264_bitstream_format(bs4, sizeof(bs4))); + assert(0 == mpeg4_h264_bitstream_format(bs5, sizeof(bs5))); + assert(3 == mpeg4_h264_bitstream_format(avcc3, sizeof(avcc3))); + assert(4 == mpeg4_h264_bitstream_format(avcc4, sizeof(avcc4))); +} + +static void mpeg4_annexbtomp4_test2(void) +{ + const uint8_t sps[] = { 0x00,0x00,0x00,0x01,0x67,0x42,0xe0,0x1e,0xab,0xcd, }; + const uint8_t pps[] = { 0x00,0x00,0x00,0x01,0x28,0xce,0x3c,0x80 }; + const uint8_t sps1[] = { 0x00,0x00,0x00,0x01,0x67,0x42,0xe0,0x1e,0x4b,0xcd, 0x01 }; + const uint8_t pps1[] = { 0x00,0x00,0x00,0x01,0x28,0xce,0x3c,0x80, 0x01 }; + const uint8_t sps2[] = { 0x00,0x00,0x00,0x01,0x67,0x42,0xe0,0x1e,0xab }; + const uint8_t pps2[] = { 0x00,0x00,0x00,0x01,0x28,0xce,0x3c }; + + int vcl, update; + uint8_t buffer[128]; + struct mpeg4_avc_t avc; + memset(&avc, 0, sizeof(avc)); + + h264_annexbtomp4(&avc, sps, sizeof(sps), buffer, sizeof(buffer), &vcl, &update); + assert(0 == vcl && 1 == update); + h264_annexbtomp4(&avc, pps, sizeof(pps), buffer, sizeof(buffer), &vcl, &update); + assert(0 == vcl && 1 == update && 1 == avc.nb_sps && avc.sps[0].bytes == sizeof(sps)-4 && 0 == memcmp(avc.sps[0].data, sps+4, sizeof(sps) - 4) && 1 == avc.nb_pps && avc.pps[0].bytes == sizeof(pps) - 4 && 0 == memcmp(avc.pps[0].data, pps+4, sizeof(pps) - 4)); + + h264_annexbtomp4(&avc, sps1, sizeof(sps1), buffer, sizeof(buffer), &vcl, &update); + assert(0 == vcl && 1 == update && 2 == avc.nb_sps && avc.sps[0].bytes == sizeof(sps) - 4 && avc.sps[1].bytes == sizeof(sps1) - 4 && 0 == memcmp(avc.sps[0].data, sps+4, sizeof(sps) - 4) && 0 == memcmp(avc.sps[1].data, sps1 + 4, sizeof(sps1) - 4) && 1 == avc.nb_pps && avc.pps[0].bytes == sizeof(pps) - 4 && 0 == memcmp(avc.pps[0].data, pps + 4, sizeof(pps) - 4)); + + h264_annexbtomp4(&avc, pps1, sizeof(pps1), buffer, sizeof(buffer), &vcl, &update); + assert(0 == vcl && 1 == update && 2 == avc.nb_sps && avc.sps[0].bytes == sizeof(sps) - 4 && avc.sps[1].bytes == sizeof(sps1) - 4 && 0 == memcmp(avc.sps[0].data, sps + 4, sizeof(sps) - 4) && 0 == memcmp(avc.sps[1].data, sps1 + 4, sizeof(sps1) - 4) && 1 == avc.nb_pps && avc.pps[0].bytes == sizeof(pps1) - 4 && 0 == memcmp(avc.pps[0].data, pps1 + 4, sizeof(pps1) - 4)); + + h264_annexbtomp4(&avc, sps2, sizeof(sps2), buffer, sizeof(buffer), &vcl, &update); + assert(0 == vcl && 1 == update && 2 == avc.nb_sps && avc.sps[0].bytes == sizeof(sps2) - 4 && avc.sps[1].bytes == sizeof(sps1) - 4 && 0 == memcmp(avc.sps[0].data, sps2 + 4, sizeof(sps2) - 4) && 0 == memcmp(avc.sps[1].data, sps1 + 4, sizeof(sps1) - 4) && 1 == avc.nb_pps && avc.pps[0].bytes == sizeof(pps1) - 4 && 0 == memcmp(avc.pps[0].data, pps1 + 4, sizeof(pps1) - 4)); + + h264_annexbtomp4(&avc, pps2, sizeof(pps2), buffer, sizeof(buffer), &vcl, &update); + assert(0 == vcl && 1 == update && 2 == avc.nb_sps && avc.sps[0].bytes == sizeof(sps2) - 4 && avc.sps[1].bytes == sizeof(sps1) - 4 && 0 == memcmp(avc.sps[0].data, sps2 + 4, sizeof(sps2) - 4) && 0 == memcmp(avc.sps[1].data, sps1 + 4, sizeof(sps1) - 4) && 1 == avc.nb_pps && avc.pps[0].bytes == sizeof(pps2) - 4 && 0 == memcmp(avc.pps[0].data, pps2 + 4, sizeof(pps2) - 4)); +} + +void mpeg4_annexbtomp4_test(void) +{ + const uint8_t sps[] = { 0x67,0x42,0xe0,0x1e,0xab }; + const uint8_t pps[] = { 0x28,0xce,0x3c,0x80 }; + const uint8_t annexb[] = { 0x00,0x00,0x00,0x01,0x67,0x42,0xe0,0x1e,0xab, 0x00,0x00,0x00,0x01,0x28,0xce,0x3c,0x80,0x00,0x00,0x00,0x01,0x65,0x11 }; + uint8_t output[256]; + int vcl, update; + + struct mpeg4_avc_t avc; + memset(&avc, 0, sizeof(avc)); + assert(h264_annexbtomp4(&avc, annexb, sizeof(annexb), output, sizeof(output), &vcl, &update) > 0); + assert(1 == avc.nb_sps && avc.sps[0].bytes == sizeof(sps) && 0 == memcmp(avc.sps[0].data, sps, sizeof(sps))); + assert(1 == avc.nb_pps && avc.pps[0].bytes == sizeof(pps) && 0 == memcmp(avc.pps[0].data, pps, sizeof(pps))); + assert(vcl == 1); + + mpeg4_annexbtomp4_test2(); + mpeg4_h264_bitstream_format_test(); +} +#endif diff --git a/MediaServer/libflv/source/mpeg4-avc.c b/MediaServer/libflv/source/mpeg4-avc.c new file mode 100644 index 0000000..f38772c --- /dev/null +++ b/MediaServer/libflv/source/mpeg4-avc.c @@ -0,0 +1,277 @@ +#include "mpeg4-avc.h" +#include +#include +#include + +/* +ISO/IEC 14496-15:2010(E) 5.2.4.1.1 Syntax (p16) + +aligned(8) class AVCDecoderConfigurationRecord { + unsigned int(8) configurationVersion = 1; + unsigned int(8) AVCProfileIndication; + unsigned int(8) profile_compatibility; + unsigned int(8) AVCLevelIndication; + bit(6) reserved = '111111'b; + unsigned int(2) lengthSizeMinusOne; + bit(3) reserved = '111'b; + + unsigned int(5) numOfSequenceParameterSets; + for (i=0; i< numOfSequenceParameterSets; i++) { + unsigned int(16) sequenceParameterSetLength ; + bit(8*sequenceParameterSetLength) sequenceParameterSetNALUnit; + } + + unsigned int(8) numOfPictureParameterSets; + for (i=0; i< numOfPictureParameterSets; i++) { + unsigned int(16) pictureParameterSetLength; + bit(8*pictureParameterSetLength) pictureParameterSetNALUnit; + } + + if( profile_idc == 100 || profile_idc == 110 || + profile_idc == 122 || profile_idc == 144 ) + { + bit(6) reserved = '111111'b; + unsigned int(2) chroma_format; + bit(5) reserved = '11111'b; + unsigned int(3) bit_depth_luma_minus8; + bit(5) reserved = '11111'b; + unsigned int(3) bit_depth_chroma_minus8; + unsigned int(8) numOfSequenceParameterSetExt; + for (i=0; i< numOfSequenceParameterSetExt; i++) { + unsigned int(16) sequenceParameterSetExtLength; + bit(8*sequenceParameterSetExtLength) sequenceParameterSetExtNALUnit; + } + } +} +*/ +static int _mpeg4_avc_decoder_configuration_record_load(const uint8_t* data, size_t bytes, struct mpeg4_avc_t* avc) +{ + uint8_t i; + uint32_t j; + uint16_t len; + uint8_t *p, *end; + + if (bytes < 7) return -1; + assert(1 == data[0]); +// avc->version = data[0]; + avc->profile = data[1]; + avc->compatibility = data[2]; + avc->level = data[3]; + avc->nalu = (data[4] & 0x03) + 1; + avc->nb_sps = data[5] & 0x1F; + if (avc->nb_sps > sizeof(avc->sps) / sizeof(avc->sps[0])) + { + assert(0); + return -1; // sps <= 32 + } + + j = 6; + p = avc->data; + end = avc->data + sizeof(avc->data); + for (i = 0; i < avc->nb_sps && j + 2 < bytes; ++i) + { + len = (data[j] << 8) | data[j + 1]; + if (j + 2 + len >= bytes || p + len > end) + { + assert(0); + return -1; + } + + memcpy(p, data + j + 2, len); + avc->sps[i].data = p; + avc->sps[i].bytes = len; + j += len + 2; + p += len; + } + + if (j >= bytes || (unsigned int)data[j] > sizeof(avc->pps) / sizeof(avc->pps[0])) + { + assert(0); + return -1; + } + + avc->nb_pps = data[j++]; + for (i = 0; i < avc->nb_pps && j + 2 < bytes; i++) + { + len = (data[j] << 8) | data[j + 1]; + if (j + 2 + len > bytes || p + len > end) + { + assert(0); + return -1; + } + + memcpy(p, data + j + 2, len); + avc->pps[i].data = p; + avc->pps[i].bytes = len; + j += len + 2; + p += len; + } + + avc->off = (int)(p - avc->data); + return j; +} + +int mpeg4_avc_decoder_configuration_record_save(const struct mpeg4_avc_t* avc, uint8_t* data, size_t bytes) +{ + uint8_t i; + uint8_t *p = data; + + assert(0 < avc->nalu && avc->nalu <= 4); + if (bytes < 7 || avc->nb_sps > 32) return -1; + bytes -= 7; + + // AVCDecoderConfigurationRecord + // ISO/IEC 14496-15:2010 + // 5.2.4.1.1 Syntax + p[0] = 1; // configurationVersion + p[1] = avc->profile; // AVCProfileIndication + p[2] = avc->compatibility; // profile_compatibility + p[3] = avc->level; // AVCLevelIndication + p[4] = 0xFC | (avc->nalu - 1); // lengthSizeMinusOne: 3 + p += 5; + + // sps + *p++ = 0xE0 | avc->nb_sps; + for (i = 0; i < avc->nb_sps && bytes >= (size_t)avc->sps[i].bytes + 2; i++) + { + *p++ = (avc->sps[i].bytes >> 8) & 0xFF; + *p++ = avc->sps[i].bytes & 0xFF; + memcpy(p, avc->sps[i].data, avc->sps[i].bytes); + + p += avc->sps[i].bytes; + bytes -= avc->sps[i].bytes + 2; + } + if (i < avc->nb_sps) return -1; // check length + + // pps + *p++ = avc->nb_pps; + for (i = 0; i < avc->nb_pps && bytes >= (size_t)avc->pps[i].bytes + 2; i++) + { + *p++ = (avc->pps[i].bytes >> 8) & 0xFF; + *p++ = avc->pps[i].bytes & 0xFF; + memcpy(p, avc->pps[i].data, avc->pps[i].bytes); + + p += avc->pps[i].bytes; + bytes -= avc->pps[i].bytes + 2; + } + if (i < avc->nb_pps) return -1; // check length + + if (bytes >= 4) + { + if (avc->profile == 100 || avc->profile == 110 || + avc->profile == 122 || avc->profile == 244 || avc->profile == 44 || + avc->profile == 83 || avc->profile == 86 || avc->profile == 118 || + avc->profile == 128 || avc->profile == 138 || avc->profile == 139 || + avc->profile == 134) + { + *p++ = 0xFC | avc->chroma_format_idc; + *p++ = 0xF8 | avc->bit_depth_luma_minus8; + *p++ = 0xF8 | avc->bit_depth_chroma_minus8; + *p++ = 0; // numOfSequenceParameterSetExt + } + } + + return (int)(p - data); +} + +#define H264_STARTCODE(p) (p[0]==0 && p[1]==0 && (p[2]==1 || (p[2]==0 && p[3]==1))) + +int mpeg4_avc_from_nalu(const uint8_t* data, size_t bytes, struct mpeg4_avc_t* avc) +{ + int r; + r = h264_annexbtomp4(avc, data, bytes, NULL, 0, NULL, NULL); + return avc->nb_sps > 0 && avc->nb_pps > 0 ? bytes : r; +} + +int mpeg4_avc_to_nalu(const struct mpeg4_avc_t* avc, uint8_t* data, size_t bytes) +{ + uint8_t i; + size_t k = 0; + uint8_t* h264 = data; + + // sps + for (i = 0; i < avc->nb_sps && bytes >= k + avc->sps[i].bytes + 4; i++) + { + if (avc->sps[i].bytes < 4 || !H264_STARTCODE(avc->sps[i].data)) + { + h264[k++] = 0; + h264[k++] = 0; + h264[k++] = 0; + h264[k++] = 1; + } + memcpy(h264 + k, avc->sps[i].data, avc->sps[i].bytes); + + k += avc->sps[i].bytes; + } + if (i < avc->nb_sps) return -1; // check length + + // pps + for (i = 0; i < avc->nb_pps && bytes >= k + avc->pps[i].bytes + 2; i++) + { + if (avc->pps[i].bytes < 4 || !H264_STARTCODE(avc->pps[i].data)) + { + h264[k++] = 0; + h264[k++] = 0; + h264[k++] = 0; + h264[k++] = 1; + } + memcpy(h264 + k, avc->pps[i].data, avc->pps[i].bytes); + + k += avc->pps[i].bytes; + } + if (i < avc->nb_pps) return -1; // check length + + assert(k < 0x7FFF); + return (int)k; +} + +int mpeg4_avc_codecs(const struct mpeg4_avc_t* avc, char* codecs, size_t bytes) +{ + // https://tools.ietf.org/html/rfc6381#section-3.3 + // https://developer.mozilla.org/en-US/docs/Web/Media/Formats/codecs_parameter + return snprintf(codecs, bytes, "avc1.%02x%02x%02x", avc->profile, avc->compatibility, avc->level); +} + +int mpeg4_avc_decoder_configuration_record_load(const uint8_t* data, size_t bytes, struct mpeg4_avc_t* avc) +{ + int r; + r = _mpeg4_avc_decoder_configuration_record_load(data, bytes, avc); + if (r > 0 && avc->nb_sps > 0 && avc->nb_pps > 0) + return r; + + // try annexb + memset(avc, 0, sizeof(*avc)); + return mpeg4_avc_from_nalu(data, bytes, avc); +} + +#if defined(_DEBUG) || defined(DEBUG) +void mpeg4_annexbtomp4_test(void); +void mpeg4_avc_test(void) +{ + const unsigned char src[] = { + 0x01,0x42,0xe0,0x1e,0xff,0xe1,0x00,0x21,0x67,0x42,0xe0,0x1e,0xab,0x40,0xf0,0x28, + 0xd0,0x80,0x00,0x00,0x00,0x80,0x00,0x00,0x19,0x70,0x20,0x00,0x78,0x00,0x00,0x0f, + 0x00,0x16,0xb1,0xb0,0x3c,0x50,0xaa,0x80,0x80,0x01,0x00,0x04,0x28,0xce,0x3c,0x80 + }; + const unsigned char nalu[] = { + 0x00,0x00,0x00,0x01,0x67,0x42,0xe0,0x1e,0xab,0x40,0xf0,0x28,0xd0,0x80,0x00,0x00, + 0x00,0x80,0x00,0x00,0x19,0x70,0x20,0x00,0x78,0x00,0x00,0x0f,0x00,0x16,0xb1,0xb0, + 0x3c,0x50,0xaa,0x80,0x80,0x00,0x00,0x00,0x01,0x28,0xce,0x3c,0x80 + }; + unsigned char data[sizeof(src)]; + + struct mpeg4_avc_t avc; + assert(sizeof(src) == mpeg4_avc_decoder_configuration_record_load(src, sizeof(src), &avc)); + assert(0x42 == avc.profile && 0xe0 == avc.compatibility && 0x1e == avc.level); + assert(4 == avc.nalu && 1 == avc.nb_sps && 1 == avc.nb_pps); + assert(sizeof(src) == mpeg4_avc_decoder_configuration_record_save(&avc, data, sizeof(data))); + assert(0 == memcmp(src, data, sizeof(src))); + mpeg4_avc_codecs(&avc, (char*)data, sizeof(data)); + assert(0 == memcmp("avc1.42e01e", data, 11)); + + assert(sizeof(nalu) == mpeg4_avc_to_nalu(&avc, data, sizeof(data))); + assert(0 == memcmp(nalu, data, sizeof(nalu))); + + mpeg4_annexbtomp4_test(); +} +#endif diff --git a/MediaServer/libflv/source/mpeg4-hevc.c b/MediaServer/libflv/source/mpeg4-hevc.c new file mode 100644 index 0000000..62498bc --- /dev/null +++ b/MediaServer/libflv/source/mpeg4-hevc.c @@ -0,0 +1,329 @@ +#include "mpeg4-hevc.h" +#include +#include +#include + +#define H265_VPS 32 +#define H265_SPS 33 +#define H265_PPS 34 +#define H265_PREFIX_SEI 39 +#define H265_SUFFIX_SEI 40 + +static uint8_t* w32(uint8_t* p, uint32_t v) +{ + *p++ = (uint8_t)(v >> 24); + *p++ = (uint8_t)(v >> 16); + *p++ = (uint8_t)(v >> 8); + *p++ = (uint8_t)v; + return p; +} + +static uint8_t* w16(uint8_t* p, uint16_t v) +{ + *p++ = (uint8_t)(v >> 8); + *p++ = (uint8_t)v; + return p; +} + +/* +ISO/IEC 14496-15:2017(E) 8.3.3.1.2 Syntax (p71) + +aligned(8) class HEVCDecoderConfigurationRecord { + unsigned int(8) configurationVersion = 1; + unsigned int(2) general_profile_space; + unsigned int(1) general_tier_flag; + unsigned int(5) general_profile_idc; + unsigned int(32) general_profile_compatibility_flags; + unsigned int(48) general_constraint_indicator_flags; + unsigned int(8) general_level_idc; + bit(4) reserved = '1111'b; + unsigned int(12) min_spatial_segmentation_idc; + bit(6) reserved = '111111'b; + unsigned int(2) parallelismType; + bit(6) reserved = '111111'b; + unsigned int(2) chromaFormat; + bit(5) reserved = '11111'b; + unsigned int(3) bitDepthLumaMinus8; + bit(5) reserved = '11111'b; + unsigned int(3) bitDepthChromaMinus8; + bit(16) avgFrameRate; + bit(2) constantFrameRate; + bit(3) numTemporalLayers; + bit(1) temporalIdNested; + unsigned int(2) lengthSizeMinusOne; + unsigned int(8) numOfArrays; + for (j=0; j < numOfArrays; j++) { + bit(1) array_completeness; + unsigned int(1) reserved = 0; + unsigned int(6) NAL_unit_type; + unsigned int(16) numNalus; + for (i=0; i< numNalus; i++) { + unsigned int(16) nalUnitLength; + bit(8*nalUnitLength) nalUnit; + } + } +} +*/ +static int _mpeg4_hevc_decoder_configuration_record_load(const uint8_t* data, size_t bytes, struct mpeg4_hevc_t* hevc) +{ + uint8_t nalutype; + uint16_t i, j, k, n, numOfArrays; + const uint8_t* p; + uint8_t* dst; + + if (bytes < 23) + return -1; + + hevc->configurationVersion = data[0]; + if (1 != hevc->configurationVersion) + return -1; + + hevc->general_profile_space = (data[1] >> 6) & 0x03; + hevc->general_tier_flag = (data[1] >> 5) & 0x01; + hevc->general_profile_idc = data[1] & 0x1F; + hevc->general_profile_compatibility_flags = (data[2] << 24) | (data[3] << 16) | (data[4] << 8) | data[5]; + hevc->general_constraint_indicator_flags = ((uint32_t)data[6] << 24) | ((uint32_t)data[7] << 16) | ((uint32_t)data[8] << 8) | (uint32_t)data[9]; + hevc->general_constraint_indicator_flags = (hevc->general_constraint_indicator_flags << 16) | (((uint64_t)data[10]) << 8) | data[11]; + hevc->general_level_idc = data[12]; + hevc->min_spatial_segmentation_idc = ((data[13] & 0x0F) << 8) | data[14]; + hevc->parallelismType = data[15] & 0x03; + hevc->chromaFormat = data[16] & 0x03; + hevc->bitDepthLumaMinus8 = data[17] & 0x07; + hevc->bitDepthChromaMinus8 = data[18] & 0x07; + hevc->avgFrameRate = (data[19] << 8) | data[20]; + hevc->constantFrameRate = (data[21] >> 6) & 0x03; + hevc->numTemporalLayers = (data[21] >> 3) & 0x07; + hevc->temporalIdNested = (data[21] >> 2) & 0x01; + hevc->lengthSizeMinusOne = data[21] & 0x03; + numOfArrays = data[22]; + + p = data + 23; + dst = hevc->data; + hevc->numOfArrays = 0; + for (i = 0; i < numOfArrays; i++) + { + if (p + 3 > data + bytes) + return -1; + + nalutype = p[0]; + n = (p[1] << 8) | p[2]; + p += 3; + + for (j = 0; j < n; j++) + { + if (hevc->numOfArrays >= sizeof(hevc->nalu) / sizeof(hevc->nalu[0])) + { + assert(0); + return -1; // too many nalu(s) + } + + if (p + 2 > data + bytes) + return -1; + + k = (p[0] << 8) | p[1]; + if (p + 2 + k > data + bytes || dst + k > hevc->data + sizeof(hevc->data)) + { + assert(0); + return -1; + } + + assert((nalutype & 0x3F) == ((p[2] >> 1) & 0x3F)); + hevc->nalu[hevc->numOfArrays].array_completeness = (nalutype >> 7) & 0x01; + hevc->nalu[hevc->numOfArrays].type = nalutype & 0x3F; + hevc->nalu[hevc->numOfArrays].bytes = k; + hevc->nalu[hevc->numOfArrays].data = dst; + memcpy(hevc->nalu[hevc->numOfArrays].data, p + 2, k); + hevc->numOfArrays++; + + p += 2 + k; + dst += k; + } + } + + hevc->off = (int)(dst - hevc->data); + return (int)(p - data); +} + +int mpeg4_hevc_decoder_configuration_record_save(const struct mpeg4_hevc_t* hevc, uint8_t* data, size_t bytes) +{ + uint16_t n; + uint8_t i, j, k; + uint8_t *ptr, *end; + uint8_t *p = data; + uint8_t array_completeness = 1; + const uint8_t nalu[] = {H265_VPS, H265_SPS, H265_PPS, H265_PREFIX_SEI, H265_SUFFIX_SEI}; + + assert(hevc->lengthSizeMinusOne <= 3); + end = data + bytes; + if (bytes < 23) + return 0; // don't have enough memory + + // HEVCDecoderConfigurationRecord + // ISO/IEC 14496-15:2017 + // 8.3.3.1.2 Syntax + assert(1 == hevc->configurationVersion); + data[0] = hevc->configurationVersion; + + // general_profile_space + general_tier_flag + general_profile_idc + data[1] = ((hevc->general_profile_space & 0x03) << 6) | ((hevc->general_tier_flag & 0x01) << 5) | (hevc->general_profile_idc & 0x1F); + + // general_profile_compatibility_flags + w32(data + 2, hevc->general_profile_compatibility_flags); + + // general_constraint_indicator_flags + w32(data + 6, (uint32_t)(hevc->general_constraint_indicator_flags >> 16)); + w16(data + 10, (uint16_t)hevc->general_constraint_indicator_flags); + + // general_level_idc + data[12] = hevc->general_level_idc; + + // min_spatial_segmentation_idc + w16(data + 13, 0xF000 | hevc->min_spatial_segmentation_idc); + + data[15] = 0xFC | hevc->parallelismType; + data[16] = 0xFC | hevc->chromaFormat; + data[17] = 0xF8 | hevc->bitDepthLumaMinus8; + data[18] = 0xF8 | hevc->bitDepthChromaMinus8; + w16(data + 19, hevc->avgFrameRate); + data[21] = (hevc->constantFrameRate << 6) | ((hevc->numTemporalLayers & 0x07) << 3) | ((hevc->temporalIdNested & 0x01) << 2) | (hevc->lengthSizeMinusOne & 0x03); +// data[22] = hevc->numOfArrays; + + p = data + 23; + for (k = i = 0; i < sizeof(nalu)/sizeof(nalu[0]) && p + 3 <= end; i++) + { + ptr = p + 3; + for (n = j = 0; j < hevc->numOfArrays; j++) + { + assert(hevc->nalu[j].type == ((hevc->nalu[j].data[0] >> 1) & 0x3F)); + if(nalu[i] != hevc->nalu[j].type) + continue; + + if (ptr + 2 + hevc->nalu[j].bytes > end) + return 0; // don't have enough memory + + array_completeness = hevc->nalu[j].array_completeness; + assert(hevc->nalu[i].data + hevc->nalu[j].bytes <= hevc->data + sizeof(hevc->data)); + w16(ptr, hevc->nalu[j].bytes); + memcpy(ptr + 2, hevc->nalu[j].data, hevc->nalu[j].bytes); + ptr += 2 + hevc->nalu[j].bytes; + n++; + } + + if (n > 0) + { + // array_completeness + NAL_unit_type + p[0] = (array_completeness << 7) | (nalu[i] & 0x3F); + w16(p + 1, n); + p = ptr; + k++; + } + } + + data[22] = k; + + return (int)(p - data); +} + +int mpeg4_hevc_from_nalu(const uint8_t* data, size_t bytes, struct mpeg4_hevc_t* hevc) +{ + int r; + r = h265_annexbtomp4(hevc, data, bytes, NULL, 0, NULL, NULL); + return hevc->numOfArrays > 1 ? bytes : r; +} + +int mpeg4_hevc_to_nalu(const struct mpeg4_hevc_t* hevc, uint8_t* data, size_t bytes) +{ + uint8_t i; + uint8_t* p, *end; + const uint8_t startcode[] = { 0, 0, 0, 1 }; + + p = data; + end = p + bytes; + + for (i = 0; i < hevc->numOfArrays; i++) + { + if (p + hevc->nalu[i].bytes + 4 > end) + return -1; + + memcpy(p, startcode, 4); + memcpy(p + 4, hevc->nalu[i].data, hevc->nalu[i].bytes); + assert(hevc->nalu[i].type == ((hevc->nalu[i].data[0] >> 1) & 0x3F)); + p += 4 + hevc->nalu[i].bytes; + } + + return (int)(p - data); +} + +int mpeg4_hevc_codecs(const struct mpeg4_hevc_t* hevc, char* codecs, size_t bytes) +{ + // ISO/IEC 14496-15:2017(E) + // Annex E Sub-parameters of the MIME type "codecs" parameter (p154) + // 'hev1.' or 'hvc1.' prefix (5 chars) + // profile, e.g. '.A12' (max 4 chars) + // profile_compatibility reserve bit order, dot + 32-bit hex number (max 9 chars) + // tier and level, e.g. '.H120' (max 5 chars) + // up to 6 constraint bytes, bytes are dot-separated and hex-encoded. + const char* tier = "LH"; + const char* space[] = { "", "A", "B", "C" }; + uint32_t x; + x = hevc->general_profile_compatibility_flags; + x = ((x >> 1) & 0x55555555) | ((x & 0x55555555) << 1); + x = ((x >> 2) & 0x33333333) | ((x & 0x33333333) << 2); + x = ((x >> 4) & 0x0f0f0f0f) | ((x & 0x0f0f0f0f) << 4); + x = ((x >> 8) & 0x00ff00ff) | ((x & 0x00ff00ff) << 8); + x = (x >> 16) | (x << 16); + return snprintf(codecs, bytes, "hvc1.%s%u.%x.%c%u", space[hevc->general_profile_space%4], (unsigned int)hevc->general_profile_idc, (unsigned int)x, tier[hevc->general_tier_flag%2], (unsigned int)hevc->general_level_idc); +} + +int mpeg4_hevc_decoder_configuration_record_load(const uint8_t* data, size_t bytes, struct mpeg4_hevc_t* hevc) +{ + int r; + r = _mpeg4_hevc_decoder_configuration_record_load(data, bytes, hevc); + if (r > 0 && hevc->numOfArrays >= 2) + return r; + + memset(hevc, 0, sizeof(*hevc)); + return mpeg4_hevc_from_nalu(data, bytes, hevc); +} + +#if defined(_DEBUG) || defined(DEBUG) +void hevc_annexbtomp4_test(void); +void mpeg4_hevc_test(void) +{ + const unsigned char src[] = { + 0x01,0x01,0x60,0x00,0x00,0x00,0x80,0x00,0x00,0x00,0x00,0x00,0xb4,0xf0,0x00, + 0xfc,0xfd,0xf8,0xf8,0x00,0x00,0x0f,0x03,0xa0,0x00,0x01,0x00,0x18,0x40,0x01, + 0x0c,0x01,0xff,0xff,0x01,0x60,0x00,0x00,0x03,0x00,0x80,0x00,0x00,0x03,0x00, + 0x00,0x03,0x00,0xb4,0x9d,0xc0,0x90,0xa1,0x00,0x01,0x00,0x29,0x42,0x01,0x01, + 0x01,0x60,0x00,0x00,0x03,0x00,0x80,0x00,0x00,0x03,0x00,0x00,0x03,0x00,0xb4, + 0xa0,0x01,0xe0,0x20,0x02,0x1c,0x59,0x67,0x79,0x24,0x6d,0xae,0x01,0x00,0x00, + 0x03,0x03,0xe8,0x00,0x00,0x5d,0xc0,0x08,0xa2,0x00,0x01,0x00,0x06,0x44,0x01, + 0xc1,0x73,0xd1,0x89 + }; + const unsigned char nalu[] = { + 0x00,0x00,0x00,0x01,0x40,0x01,0x0c,0x01,0xff,0xff,0x01,0x60,0x00,0x00,0x03, + 0x00,0x80,0x00,0x00,0x03,0x00,0x00,0x03,0x00,0xb4,0x9d,0xc0,0x90,0x00,0x00, + 0x00,0x01,0x42,0x01,0x01,0x01,0x60,0x00,0x00,0x03,0x00,0x80,0x00,0x00,0x03, + 0x00,0x00,0x03,0x00,0xb4,0xa0,0x01,0xe0,0x20,0x02,0x1c,0x59,0x67,0x79,0x24, + 0x6d,0xae,0x01,0x00,0x00,0x03,0x03,0xe8,0x00,0x00,0x5d,0xc0,0x08,0x00,0x00, + 0x00,0x01,0x44,0x01,0xc1,0x73,0xd1,0x89 + }; + unsigned char data[sizeof(src)]; + + struct mpeg4_hevc_t hevc; + assert(sizeof(src) == mpeg4_hevc_decoder_configuration_record_load(src, sizeof(src), &hevc)); + assert(0 == hevc.general_profile_space && 0 == hevc.general_tier_flag); + assert(1 == hevc.general_profile_idc && 0xb4 == hevc.general_level_idc); + assert(1 == hevc.numTemporalLayers && 1 == hevc.temporalIdNested); + assert(3 == hevc.numOfArrays); + assert(sizeof(src) == mpeg4_hevc_decoder_configuration_record_save(&hevc, data, sizeof(data))); + assert(0 == memcmp(src, data, sizeof(src))); + mpeg4_hevc_codecs(&hevc, (char*)data, sizeof(data)); + assert(0 == memcmp("hvc1.1.6.L180", data, 13)); + + assert(sizeof(nalu) == mpeg4_hevc_to_nalu(&hevc, data, sizeof(data))); + assert(0 == memcmp(nalu, data, sizeof(nalu))); + + hevc_annexbtomp4_test(); +} +#endif diff --git a/MediaServer/libflv/source/mpeg4-mp4toannexb.c b/MediaServer/libflv/source/mpeg4-mp4toannexb.c new file mode 100644 index 0000000..94d3ede --- /dev/null +++ b/MediaServer/libflv/source/mpeg4-mp4toannexb.c @@ -0,0 +1,170 @@ +// ISO/IEC 14496-1:2010(E) +// Annex I: Usage of ITU-T Recommendation H.264 | ISO/IEC 14496-10 AVC (p150) + +#include "mpeg4-avc.h" +#include +#include +#include +#include +#include + +#define H264_NAL_IDR 5 // Coded slice of an IDR picture +#define H264_NAL_SPS 7 // Sequence parameter set +#define H264_NAL_PPS 8 // Picture parameter set +#define H264_NAL_AUD 9 // Access unit delimiter + +struct h264_mp4toannexb_handle_t +{ + const struct mpeg4_avc_t* avc; + int sps_pps_flag; + int errcode; + + uint8_t* out; + size_t bytes; + size_t capacity; +}; + +static int h264_sps_pps_size(const struct mpeg4_avc_t* avc) +{ + int i, n = 0; + for (i = 0; i < avc->nb_sps; i++) + n += avc->sps[i].bytes + 4; + for (i = 0; i < avc->nb_pps; i++) + n += avc->pps[i].bytes + 4; + return n; +} + +static void h264_mp4toannexb_handler(void* param, const uint8_t* nalu, size_t bytes) +{ + int n; + const uint8_t h264_start_code[] = { 0x00, 0x00, 0x00, 0x01 }; + struct h264_mp4toannexb_handle_t* mp4; + mp4 = (struct h264_mp4toannexb_handle_t*)param; + + if (bytes < 1) + { + assert(0); + mp4->errcode = -EINVAL; + return; + } + + // insert SPS/PPS before IDR frame + switch (nalu[0] & 0x1f) + { + case H264_NAL_SPS: + case H264_NAL_PPS: + //flv->data[k++] = 0; // SPS/PPS add zero_byte(ITU H.264 B.1.2 Byte stream NAL unit semantics) + mp4->sps_pps_flag = 1; + break; + + case H264_NAL_IDR: + if (0 == mp4->sps_pps_flag) + { + if (mp4->bytes > 0) + { + // write sps/pps at first + n = h264_sps_pps_size(mp4->avc); + if (n + mp4->bytes > mp4->capacity) + { + mp4->errcode = -E2BIG; + return; + } + memmove(mp4->out + n, mp4->out, mp4->bytes); + } + n = mpeg4_avc_to_nalu(mp4->avc, mp4->out, mp4->capacity); + if (n <= 0) + { + mp4->errcode = 0 == n ? -EINVAL : n; + return; + } + mp4->bytes += n; + mp4->sps_pps_flag = 1; // don't insert more than one-times + } + break; + +#if defined(H2645_FILTER_AUD) + case H264_NAL_AUD: + continue; // ignore AUD +#endif + } + + if (mp4->bytes + bytes + sizeof(h264_start_code) > mp4->capacity) + { + mp4->errcode = -E2BIG; + return; + } + + memcpy(mp4->out + mp4->bytes, h264_start_code, sizeof(h264_start_code)); + memcpy(mp4->out + mp4->bytes + sizeof(h264_start_code), nalu, bytes); + mp4->bytes += sizeof(h264_start_code) + bytes; +} + +int h264_mp4toannexb(const struct mpeg4_avc_t* avc, const void* data, size_t bytes, void* out, size_t size) +{ + int i, n; + const uint8_t* src, *end; + struct h264_mp4toannexb_handle_t h; + + memset(&h, 0, sizeof(h)); + h.avc = avc; + h.out = (uint8_t*)out; + h.capacity = size; + + end = (const uint8_t*)data + bytes; + for (src = (const uint8_t*)data; src + avc->nalu < end; src += n + avc->nalu) + { + for (n = i = 0; i < avc->nalu; i++) + n = (n << 8) + ((uint8_t*)src)[i]; + + // fix 0x00 00 00 01 => flv nalu size + if (0 == avc->nalu || (1 == n && (3 == avc->nalu || 4 == avc->nalu))) + { + //n = (int)(end - src) - avc->nalu; + mpeg4_h264_annexb_nalu(src, end - src, h264_mp4toannexb_handler, &h); + src = end; + break; + } + + if (n <= 0 || src + n + avc->nalu > end) + { + assert(0); + return -EINVAL; + } + + h264_mp4toannexb_handler(&h, src + avc->nalu, n); + } + + assert(src == end); + return 0 == h.errcode ? (int)h.bytes : 0; +} + +#if defined(DEBUG) || defined(_DEBUG) +void h264_mp4toannexb_test(void) +{ + const uint8_t data[] = { + 0x01, 0x42, 0xe0, 0x1e, 0xff, 0xe1, 0x00, 0x21, 0x67, 0x42, 0xe0, 0x1e, 0xab, 0x40, 0xf0, 0x28, + 0xd0, 0x80, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x19, 0x70, 0x20, 0x00, 0x78, 0x00, 0x00, 0x0f, + 0x00, 0x16, 0xb1, 0xb0, 0x3c, 0x50, 0xaa, 0x80, 0x80, 0x01, 0x00, 0x04, 0x28, 0xce, 0x3c, 0x80, + }; + + const uint8_t mp4[] = { + 0x00, 0x00, 0x00, 0x08, 0x65, 0x88, 0x84, 0x01, 0x7f, 0xec, 0x05, 0x17, 0x00, 0x00, 0x00, 0x01, 0xab, + }; + + const uint8_t annexb[] = { + 0x00, 0x00, 0x00, 0x01, 0x67, 0x42, 0xe0, 0x1e, 0xab, 0x40, 0xf0, 0x28, 0xd0, 0x80, 0x00, 0x00, + 0x00, 0x80, 0x00, 0x00, 0x19, 0x70, 0x20, 0x00, 0x78, 0x00, 0x00, 0x0f, 0x00, 0x16, 0xb1, 0xb0, + 0x3c, 0x50, 0xaa, 0x80, 0x80, + 0x00, 0x00, 0x00, 0x01, 0x28, 0xce, 0x3c, 0x80, + 0x00, 0x00, 0x00, 0x01, 0x65, 0x88, 0x84, 0x01, 0x7f, 0xec, 0x05, 0x17, 0x00, 0x00, 0x00, 0x01, 0xab, + }; + + int n; + uint8_t out[sizeof(annexb) + 64]; + struct mpeg4_avc_t avc; + memset(&avc, 0, sizeof(avc)); + assert(sizeof(data) == mpeg4_avc_decoder_configuration_record_load(data, sizeof(data), &avc)); + n = h264_mp4toannexb(&avc, mp4, sizeof(mp4), out, sizeof(out)); + assert(n == sizeof(annexb) && 0 == memcmp(annexb, out, n)); +} +#endif \ No newline at end of file diff --git a/MediaServer/libflv/source/mpeg4-vvc.c b/MediaServer/libflv/source/mpeg4-vvc.c new file mode 100644 index 0000000..983f45e --- /dev/null +++ b/MediaServer/libflv/source/mpeg4-vvc.c @@ -0,0 +1,427 @@ +#include "mpeg4-vvc.h" +#include "mpeg4-bits.h" +#include +#include +#include +#include + +#define H266_OPI 12 +#define H266_DCI 13 +#define H266_VPS 14 +#define H266_SPS 15 +#define H266_PPS 16 +#define H266_AUD 20 +#define H266_PREFIX_SEI 23 +#define H266_SUFFIX_SEI 24 + +/* +* ISO/IEC 14496-15:2021 11.2.4.2.2 Syntax (p156) + +aligned(8) class VvcPTLRecord(num_sublayers) { + bit(2) reserved = 0; + unsigned int(6) num_bytes_constraint_info; + unsigned int(7) general_profile_idc; + unsigned int(1) general_tier_flag; + unsigned int(8) general_level_idc; + unsigned int(1) ptl_frame_only_constraint_flag; + unsigned int(1) ptl_multi_layer_enabled_flag; + unsigned int(8*num_bytes_constraint_info - 2) general_constraint_info; + for (i=num_sublayers - 2; i >= 0; i--) + unsigned int(1) ptl_sublayer_level_present_flag[i]; + for (j=num_sublayers; j<=8 && num_sublayers > 1; j++) + bit(1) ptl_reserved_zero_bit = 0; + for (i=num_sublayers-2; i >= 0; i--) + if (ptl_sublayer_level_present_flag[i]) + unsigned int(8) sublayer_level_idc[i]; + unsigned int(8) ptl_num_sub_profiles; + for (j=0; j < ptl_num_sub_profiles; j++) + unsigned int(32) general_sub_profile_idc[j]; +} +*/ + +static int mpeg4_vvc_ptl_record_load(struct mpeg4_bits_t* bits, struct mpeg4_vvc_t* vvc) +{ + int i; + mpeg4_bits_read_n(bits, 2); // reserved + vvc->native_ptl.num_bytes_constraint_info = mpeg4_bits_read_uint32(bits, 6); + vvc->native_ptl.general_profile_idc = mpeg4_bits_read_uint32(bits, 7); + vvc->native_ptl.general_tier_flag = mpeg4_bits_read_uint32(bits, 1); + vvc->native_ptl.general_level_idc = mpeg4_bits_read_uint32(bits, 8); + for (i = 0; i < (int)vvc->native_ptl.num_bytes_constraint_info && i < sizeof(vvc->native_ptl.general_constraint_info)/sizeof(vvc->native_ptl.general_constraint_info[0]); i++) + { + vvc->native_ptl.general_constraint_info[i] = mpeg4_bits_read_uint8(bits, 8); + } + vvc->native_ptl.ptl_frame_only_constraint_flag = (vvc->native_ptl.general_constraint_info[0] & 0x80) ? 1 : 0; + vvc->native_ptl.ptl_multi_layer_enabled_flag = (vvc->native_ptl.general_constraint_info[0] & 0x40) ? 1 : 0; + + vvc->native_ptl.ptl_sublayer_level_present_flag = 0; + assert(vvc->num_sublayers >= 0 && vvc->num_sublayers <= 8); + for (i = (int)vvc->num_sublayers - 2; i >= 0; i-=8) + vvc->native_ptl.ptl_sublayer_level_present_flag = mpeg4_bits_read_uint8(bits, 8); + + for (i = (int)vvc->num_sublayers - 2; i >= 0 && i < sizeof(vvc->native_ptl.sublayer_level_idc)/sizeof(vvc->native_ptl.sublayer_level_idc[0]); i--) + { + if(vvc->native_ptl.ptl_sublayer_level_present_flag & (1 << i)) + vvc->native_ptl.sublayer_level_idc[i] = mpeg4_bits_read_uint8(bits, 8); + } + + vvc->native_ptl.ptl_num_sub_profiles = mpeg4_bits_read_uint8(bits, 8); + vvc->native_ptl.general_sub_profile_idc = (uint32_t*)(vvc->data + vvc->off); + vvc->off += 4 * vvc->native_ptl.ptl_num_sub_profiles; + for (i = 0; i < vvc->native_ptl.ptl_num_sub_profiles; i++) + { + vvc->native_ptl.general_sub_profile_idc[i] = mpeg4_bits_read_uint32(bits, 32); + } + + return mpeg4_bits_error(bits); +} + +static int mpeg4_vvc_ptl_record_save(struct mpeg4_bits_t* bits, const struct mpeg4_vvc_t* vvc) +{ + int i; + mpeg4_bits_write_n(bits, 0, 2); // reserved + mpeg4_bits_write_n(bits, vvc->native_ptl.num_bytes_constraint_info, 6); + mpeg4_bits_write_n(bits, vvc->native_ptl.general_profile_idc, 7); + mpeg4_bits_write_n(bits, vvc->native_ptl.general_tier_flag, 1); + mpeg4_bits_write_n(bits, vvc->native_ptl.general_level_idc, 8); + mpeg4_bits_write_n(bits, vvc->native_ptl.ptl_frame_only_constraint_flag, 1); + mpeg4_bits_write_n(bits, vvc->native_ptl.ptl_multi_layer_enabled_flag, 1); + for (i = 0; i < (int)vvc->native_ptl.num_bytes_constraint_info; i++) + { + mpeg4_bits_write_n(bits, vvc->native_ptl.general_constraint_info[i], i + 1 < (int)vvc->native_ptl.num_bytes_constraint_info ? 8 : 6); + } + + assert(vvc->num_sublayers >= 0 && vvc->num_sublayers <= 8); + for (i = (int)vvc->num_sublayers - 2; i >= 0; i -= 8) + mpeg4_bits_write_n(bits, vvc->native_ptl.ptl_sublayer_level_present_flag, 8); + + for (i = (int)vvc->num_sublayers - 2; i >= 0 && i < sizeof(vvc->native_ptl.sublayer_level_idc) / sizeof(vvc->native_ptl.sublayer_level_idc[0]); i--) + { + if (vvc->native_ptl.ptl_sublayer_level_present_flag & (1 << i)) + mpeg4_bits_write_uint8(bits, vvc->native_ptl.sublayer_level_idc[i], 8); + } + + mpeg4_bits_write_uint8(bits, vvc->native_ptl.ptl_num_sub_profiles, 8); + for (i = 0; i < vvc->native_ptl.ptl_num_sub_profiles; i++) + { + mpeg4_bits_write_uint32(bits, vvc->native_ptl.general_sub_profile_idc[i], 32); + } + + return mpeg4_bits_error(bits); +} + +/* +* ISO/IEC 14496-15:2021 11.2.4.2.2 Syntax (p156) + +aligned(8) class VvcDecoderConfigurationRecord { + bit(5) reserved = '11111'b; + unsigned int(2) LengthSizeMinusOne; + unsigned int(1) ptl_present_flag; + if (ptl_present_flag) { + unsigned int(9) ols_idx; + unsigned int(3) num_sublayers; + unsigned int(2) constant_frame_rate; + unsigned int(2) chroma_format_idc; + unsigned int(3) bit_depth_minus8; + bit(5) reserved = '11111'b; + VvcPTLRecord(num_sublayers) native_ptl; + unsigned_int(16) max_picture_width; + unsigned_int(16) max_picture_height; + unsigned int(16) avg_frame_rate; + } + unsigned int(8) num_of_arrays; + for (j=0; j < num_of_arrays; j++) { + unsigned int(1) array_completeness; + bit(2) reserved = 0; + unsigned int(5) NAL_unit_type; + if (NAL_unit_type != DCI_NUT && NAL_unit_type != OPI_NUT) + unsigned int(16) num_nalus; + for (i=0; i< num_nalus; i++) { + unsigned int(16) nal_unit_length; + bit(8*nal_unit_length) nal_unit; + } + } +} +*/ +int mpeg4_vvc_decoder_configuration_record_load(const uint8_t* data, size_t bytes, struct mpeg4_vvc_t* vvc) +{ + struct mpeg4_bits_t bits; + uint8_t nalutype; + uint16_t i, j, k, n, numOfArrays; + uint8_t* dst; + + vvc->off = 0; // clear + mpeg4_bits_init(&bits, (void*)data, bytes); + mpeg4_bits_read_n(&bits, 5); // reserved '11111'b + vvc->lengthSizeMinusOne = mpeg4_bits_read_uint32(&bits, 2); + vvc->ptl_present_flag = mpeg4_bits_read(&bits); + if (vvc->ptl_present_flag) + { + vvc->ols_idx = mpeg4_bits_read_uint32(&bits, 9); + vvc->num_sublayers = mpeg4_bits_read_uint32(&bits, 3); + vvc->constant_frame_rate = mpeg4_bits_read_uint32(&bits, 2); + vvc->chroma_format_idc = mpeg4_bits_read_uint32(&bits, 2); + vvc->bit_depth_minus8 = mpeg4_bits_read_uint32(&bits, 3); + mpeg4_bits_read_n(&bits, 5); // reserved '11111'b + mpeg4_vvc_ptl_record_load(&bits, vvc); + vvc->max_picture_width = mpeg4_bits_read_uint16(&bits, 16); + vvc->max_picture_height = mpeg4_bits_read_uint16(&bits, 16); + vvc->avg_frame_rate = mpeg4_bits_read_uint16(&bits, 16); + } + + if (0 != mpeg4_bits_error(&bits)) + { + assert(0); + return -1; + } + + assert(0 == bits.bits % 8); + dst = vvc->data + vvc->off; + + numOfArrays = mpeg4_bits_read_uint8(&bits, 8); + for (i = 0; i < numOfArrays && 0 == mpeg4_bits_error(&bits); i++) + { + nalutype = mpeg4_bits_read_uint8(&bits, 8); + + n = 1; + if ((nalutype & 0x1f) != H266_DCI && (nalutype & 0x1f) != H266_OPI) + n = mpeg4_bits_read_uint16(&bits, 16); + + for (j = 0; j < n; j++) + { + if (vvc->numOfArrays >= sizeof(vvc->nalu) / sizeof(vvc->nalu[0])) + { + assert(0); + return -E2BIG; // too many nalu(s) + } + + k = mpeg4_bits_read_uint16(&bits, 16); + vvc->nalu[vvc->numOfArrays].array_completeness = (nalutype >> 7) & 0x01; + vvc->nalu[vvc->numOfArrays].type = nalutype & 0x1F; + vvc->nalu[vvc->numOfArrays].bytes = k; + vvc->nalu[vvc->numOfArrays].data = dst; + memcpy(vvc->nalu[vvc->numOfArrays].data, data + bits.bits / 8, k); + vvc->numOfArrays++; + + mpeg4_bits_skip(&bits, (uint64_t)k * 8); + dst += k; + } + } + + vvc->off = (int)(dst - vvc->data); + return mpeg4_bits_error(&bits) ? -1 : (int)(bits.bits / 8); +} + +int mpeg4_vvc_decoder_configuration_record_save(const struct mpeg4_vvc_t* vvc, uint8_t* data, size_t bytes) +{ + uint16_t n; + uint8_t i, j, k; + uint8_t* ptr, * end; + uint8_t* p; + uint8_t array_completeness = 1; + struct mpeg4_bits_t bits; + const uint8_t nalu[] = { H266_OPI, H266_DCI, H266_VPS, H266_SPS, H266_PPS, H266_PREFIX_SEI, H266_SUFFIX_SEI }; + + assert(vvc->lengthSizeMinusOne <= 3); + memset(data, 0, bytes); + mpeg4_bits_init(&bits, (void*)data, bytes); + mpeg4_bits_write_n(&bits, 0x1F, 5); + mpeg4_bits_write_n(&bits, vvc->lengthSizeMinusOne, 2); + mpeg4_bits_write_n(&bits, vvc->ptl_present_flag, 1); + + if (vvc->ptl_present_flag) + { + mpeg4_bits_write_n(&bits, vvc->ols_idx, 9); + mpeg4_bits_write_n(&bits, vvc->num_sublayers, 3); + mpeg4_bits_write_n(&bits, vvc->constant_frame_rate, 2); + mpeg4_bits_write_n(&bits, vvc->chroma_format_idc, 2); + mpeg4_bits_write_n(&bits, vvc->bit_depth_minus8, 3); + mpeg4_bits_write_n(&bits, 0x1F, 5); + mpeg4_vvc_ptl_record_save(&bits, vvc); + mpeg4_bits_write_uint16(&bits, vvc->max_picture_width, 16); + mpeg4_bits_write_uint16(&bits, vvc->max_picture_height, 16); + mpeg4_bits_write_uint16(&bits, vvc->avg_frame_rate, 16); + } + + if (0 != mpeg4_bits_error(&bits) || 0 != bits.bits % 8) + { + assert(0); + return -1; + } + + //mpeg4_bits_write_uint8(&bits, vvc->numOfArrays, 8); + p = data + bits.bits / 8 + 1 /*num_of_arrays*/; + end = data + bytes; + for (k = i = 0; i < sizeof(nalu) / sizeof(nalu[0]) && p + 5 <= end; i++) + { + ptr = p + 3; + for (n = j = 0; j < vvc->numOfArrays; j++) + { + if (nalu[i] != vvc->nalu[j].type) + continue; + + if (ptr + 2 + vvc->nalu[j].bytes > end) + return 0; // don't have enough memory + + array_completeness = vvc->nalu[j].array_completeness; + assert(vvc->nalu[i].data + vvc->nalu[j].bytes <= vvc->data + sizeof(vvc->data)); + ptr[0] = (vvc->nalu[j].bytes >> 8) & 0xFF; + ptr[1] = vvc->nalu[j].bytes & 0xFF; + memcpy(ptr + 2, vvc->nalu[j].data, vvc->nalu[j].bytes); + ptr += 2 + vvc->nalu[j].bytes; + n++; + } + + if (n > 0) + { + // array_completeness + NAL_unit_type + p[0] = (array_completeness << 7) | (nalu[i] & 0x1F); + p[1] = (n >> 8) & 0xFF; + p[2] = n & 0xFF; + p = ptr; + k++; + } + } + + data[bits.bits / 8] = k; // num_of_arrays + + return mpeg4_bits_error(&bits) ? -1 : (int)(p - data); +} + +int mpeg4_vvc_to_nalu(const struct mpeg4_vvc_t* vvc, uint8_t* data, size_t bytes) +{ + uint8_t i; + uint8_t* p, * end; + const uint8_t startcode[] = { 0, 0, 0, 1 }; + + p = data; + end = p + bytes; + + for (i = 0; i < vvc->numOfArrays; i++) + { + if (p + vvc->nalu[i].bytes + 4 > end || vvc->nalu[i].bytes < 2) + return -1; + + memcpy(p, startcode, 4); + memcpy(p + 4, vvc->nalu[i].data, vvc->nalu[i].bytes); + assert(vvc->nalu[i].type == ((vvc->nalu[i].data[1] >> 3) & 0x1F)); + p += 4 + vvc->nalu[i].bytes; + } + + return (int)(p - data); +} + +// RFC4648 +static size_t base32_encode(char* target, const void* source, size_t bytes) +{ + size_t i, j; + const uint8_t* ptr = (const uint8_t*)source; + static const char* s_base32_enc = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567"; + + for (j = i = 0; i < bytes / 5 * 5; i += 5) + { + target[j++] = s_base32_enc[(ptr[i] >> 3) & 0x1F]; /* c1 */ + target[j++] = s_base32_enc[((ptr[i] & 0x07) << 2) | ((ptr[i + 1] >> 6) & 0x03)]; /*c2*/ + target[j++] = s_base32_enc[(ptr[i + 1] >> 1) & 0x1F];/*c3*/ + target[j++] = s_base32_enc[((ptr[i + 1] & 0x01) << 4) | ((ptr[i + 2] >> 4) & 0x0F)]; /*c4*/ + target[j++] = s_base32_enc[((ptr[i + 2] & 0x0F) << 1) | ((ptr[i + 3] >> 7) & 0x01)]; /*c5*/ + target[j++] = s_base32_enc[(ptr[i + 3] >> 2) & 0x1F];/*c6*/ + target[j++] = s_base32_enc[((ptr[i + 3] & 0x03) << 3) | ((ptr[i + 4] >> 5) & 0x07)]; /*c7*/ + target[j++] = s_base32_enc[ptr[i + 4] & 0x1F]; /* c8 */ + } + + if (i + 1 == bytes) + { + target[j++] = s_base32_enc[(ptr[i] >> 3) & 0x1F]; /* c1 */ + target[j++] = s_base32_enc[((ptr[i] & 0x07) << 2)]; /*c2*/ + } + else if (i + 2 == bytes) + { + target[j++] = s_base32_enc[(ptr[i] >> 3) & 0x1F]; /* c1 */ + target[j++] = s_base32_enc[((ptr[i] & 0x07) << 2) | ((ptr[i + 1] >> 6) & 0x03)]; /*c2*/ + target[j++] = s_base32_enc[(ptr[i + 1] >> 1) & 0x1F];/*c3*/ + target[j++] = s_base32_enc[((ptr[i + 1] & 0x01) << 4)]; /*c4*/ + } + else if (i + 3 == bytes) + { + target[j++] = s_base32_enc[(ptr[i] >> 3) & 0x1F]; /* c1 */ + target[j++] = s_base32_enc[((ptr[i] & 0x07) << 2) | ((ptr[i + 1] >> 6) & 0x03)]; /*c2*/ + target[j++] = s_base32_enc[(ptr[i + 1] >> 1) & 0x1F];/*c3*/ + target[j++] = s_base32_enc[((ptr[i + 1] & 0x01) << 4) | ((ptr[i + 2] >> 4) & 0x0F)]; /*c4*/ + target[j++] = s_base32_enc[((ptr[i + 2] & 0x0F) << 1)]; /*c5*/ + } + else if (i + 4 == bytes) + { + target[j++] = s_base32_enc[(ptr[i] >> 3) & 0x1F]; /* c1 */ + target[j++] = s_base32_enc[((ptr[i] & 0x07) << 2) | ((ptr[i + 1] >> 6) & 0x03)]; /*c2*/ + target[j++] = s_base32_enc[(ptr[i + 1] >> 1) & 0x1F];/*c3*/ + target[j++] = s_base32_enc[((ptr[i + 1] & 0x01) << 4) | ((ptr[i + 2] >> 4) & 0x0F)]; /*c4*/ + target[j++] = s_base32_enc[((ptr[i + 2] & 0x0F) << 1) | ((ptr[i + 3] >> 7) & 0x01)]; /*c5*/ + target[j++] = s_base32_enc[(ptr[i + 3] >> 2) & 0x1F];/*c6*/ + target[j++] = s_base32_enc[((ptr[i + 3] & 0x03) << 3)]; /*c7*/ + } + + while (0 != (j % 8)) + { + target[j++] = '='; + } + + return j; +} + +int mpeg4_vvc_codecs(const struct mpeg4_vvc_t* vvc, char* codecs, size_t bytes) +{ + // ISO/IEC 14496-15:2021 + // Annex E Sub-parameters of the MIME type "codecs" parameter (p276) + // 'vvc1.' or 'vvi1.' prefix (5 chars) + int i, n; + char buffer[129]; + + // 1. trailing zero bits of the general_constraint_info() syntax structure may be omitted from the input bits to base32 encoding + n = (int)vvc->native_ptl.num_bytes_constraint_info; + for (i = (int)vvc->native_ptl.num_bytes_constraint_info - 1; i >= 0 && n > 1; i--) + { + if (0 == vvc->native_ptl.general_constraint_info[i]) + n--; + else + break; + } + i = base32_encode(buffer, vvc->native_ptl.general_constraint_info, n); + //2, the trailing padding with the "=" character may be omitted from the base32 string; + while (i > 0 && buffer[i - 1] == '=') i--; + + return snprintf(codecs, bytes, "vvc1.%u.%c%u.C%.*s", + (unsigned int)vvc->native_ptl.general_profile_idc, + vvc->native_ptl.general_tier_flag ? 'H' : 'L', vvc->native_ptl.general_level_idc, i, buffer); +} + +#if defined(_DEBUG) || defined(DEBUG) +void vvc_annexbtomp4_test(void); +static void mpeg4_vvc_codecs_test(struct mpeg4_vvc_t* vvc) +{ + int r; + char buffer[129]; + //const char* s = "vvc1.1.L51.CQA.O1+3"; + //const char* s1 = "vvc1.17.L83.CYA.O1+3"; + //const char* s2 = "vvc1.17.L83.CYA.O1+3"; + const char* s3 = "vvc1.1.L105.CAA"; + r = mpeg4_vvc_codecs(vvc, buffer, sizeof(buffer)); + assert(r == strlen(s3) && 0 == memcmp(buffer, s3, r)); +} + +void mpeg4_vvc_test(void) +{ + const uint8_t data[] = { 0xff, 0x00, 0x11, 0x1f, 0x01, 0x02, 0x69, 0x00, 0x00, 0x02, 0xd0, 0x05, 0x00, 0x00, 0x00, 0x02, 0x8f, 0x00, 0x01, 0x00, 0x2a, 0x00, 0x79, 0x00, 0x0b, 0x02, 0x69, 0x00, 0x00, 0x03, 0x00, 0x16, 0x88, 0x01, 0x40, 0x48, 0x80, 0x2b, 0x49, 0xff, 0x45, 0x19, 0x18, 0xe0, 0x0c, 0x42, 0x55, 0x5a, 0xab, 0xd5, 0xeb, 0x33, 0x25, 0x5a, 0x12, 0xe4, 0x72, 0xd4, 0x56, 0x5a, 0x32, 0x30, 0x40, 0x90, 0x00, 0x01, 0x00, 0x0c, 0x00, 0x81, 0x00, 0x00, 0x0b, 0x44, 0x00, 0xa0, 0x22, 0x24, 0x18, 0x20 }; + uint8_t buffer[sizeof(data)]; + struct mpeg4_vvc_t vvc; + memset(&vvc, 0, sizeof(vvc)); + assert(sizeof(data) == mpeg4_vvc_decoder_configuration_record_load(data, sizeof(data), &vvc)); + assert(3 == vvc.lengthSizeMinusOne && 1 == vvc.ptl_present_flag && 1 == vvc.num_sublayers); + assert(1 == vvc.chroma_format_idc && 0 == vvc.bit_depth_minus8); + assert(720 == vvc.max_picture_width && 1280 == vvc.max_picture_height && 0 == vvc.avg_frame_rate); + assert(1 == vvc.native_ptl.num_bytes_constraint_info && 1 == vvc.native_ptl.general_profile_idc && 0 == vvc.native_ptl.general_tier_flag && 0x69 == vvc.native_ptl.general_level_idc); + assert(2 == vvc.numOfArrays && H266_SPS == vvc.nalu[0].type && 0x2a == vvc.nalu[0].bytes && H266_PPS == vvc.nalu[1].type && 0x0c == vvc.nalu[1].bytes); + assert(sizeof(data) == mpeg4_vvc_decoder_configuration_record_save(&vvc, buffer, sizeof(buffer)) && 0 == memcmp(buffer, data, sizeof(data))); + mpeg4_vvc_codecs_test(&vvc); +} +#endif diff --git a/MediaServer/libflv/source/opus-head.c b/MediaServer/libflv/source/opus-head.c new file mode 100644 index 0000000..d1387cc --- /dev/null +++ b/MediaServer/libflv/source/opus-head.c @@ -0,0 +1,419 @@ +#include "opus-head.h" +#include +#include +#include + +// http://www.opus-codec.org/docs/opus_in_isobmff.html +// 4.3.2 Opus Specific Box +/* +class ChannelMappingTable (unsigned int(8) OutputChannelCount){ + unsigned int(8) StreamCount; + unsigned int(8) CoupledCount; + unsigned int(8 * OutputChannelCount) ChannelMapping; +} + +aligned(8) class OpusSpecificBox extends Box('dOps'){ + unsigned int(8) Version; + unsigned int(8) OutputChannelCount; + unsigned int(16) PreSkip; + unsigned int(32) InputSampleRate; + signed int(16) OutputGain; + unsigned int(8) ChannelMappingFamily; + if (ChannelMappingFamily != 0) { + ChannelMappingTable(OutputChannelCount); + } +} +*/ + +static const uint8_t opus_coupled_stream_cnt[9] = { + 1, 0, 1, 1, 2, 2, 2, 3, 3 +}; + +static const uint8_t opus_stream_cnt[9] = { + 1, 1, 1, 2, 2, 3, 4, 4, 5, +}; + +static const uint8_t opus_channel_map[8][8] = { + { 0 }, + { 0,1 }, + { 0,2,1 }, + { 0,1,2,3 }, + { 0,4,1,2,3 }, + { 0,4,1,2,3,5 }, + { 0,4,1,2,3,5,6 }, + { 0,6,1,2,3,4,5,7 }, +}; + +int opus_head_save(const struct opus_head_t* opus, uint8_t* data, size_t bytes) +{ + if (bytes < 19) + return -1; + + memcpy(data, "OpusHead", 8); + data[8] = opus->version; // 0 only + data[9] = opus->channels; + data[11] = (uint8_t)(opus->pre_skip >> 8); // LSB + data[10] = (uint8_t)opus->pre_skip; + data[15] = (uint8_t)(opus->input_sample_rate >> 24); // LSB + data[14] = (uint8_t)(opus->input_sample_rate >> 16); + data[13] = (uint8_t)(opus->input_sample_rate >> 8); + data[12] = (uint8_t)opus->input_sample_rate; + data[17] = (uint8_t)(opus->output_gain >> 8); // LSB + data[16] = (uint8_t)opus->output_gain; + data[18] = opus->channel_mapping_family; + if (0 != opus->channel_mapping_family && bytes >= 29) + { + data[19] = opus->stream_count; + data[20] = opus->coupled_count; + memcpy(data+21, opus->channel_mapping, 8); + return 29; + } + + return 19; +} + +int opus_head_load(const uint8_t* data, size_t bytes, struct opus_head_t* opus) +{ + int n = 0; + if (bytes > 8 && 0 == memcmp(data, "OpusHead", 8)) + { + n = 8; + data += 8; + bytes -= 8; + } + + // check channels: [1, 8] + if (bytes < 11 || data[1] > 8 || data[1] < 1) + return -1; + + memset(opus, 0, sizeof(*opus)); + opus->version = data[0]; + opus->channels = data[1]; + opus->pre_skip = ((uint16_t)data[3] << 8) | data[2]; + opus->input_sample_rate = ((uint32_t)data[7] << 24) | ((uint32_t)data[6] << 16) | ((uint32_t)data[5] << 8) | data[4]; + opus->output_gain = ((uint16_t)data[9] << 8) | data[8]; + opus->channel_mapping_family = data[10]; + + if (0 != opus->channel_mapping_family && bytes >= 21) + { + opus->stream_count = data[11]; + opus->coupled_count = data[12]; + memcpy(opus->channel_mapping, data+13, 8); + return 21 + n; + } + else + { + opus->stream_count = opus_stream_cnt[opus->channels]; + opus->coupled_count = opus_coupled_stream_cnt[opus->channels]; + memcpy(opus->channel_mapping, opus_channel_map[opus_head_channels(opus)-1], 8); + } + + return 11 + n; +} + +static const uint8_t* opus_parse_size(const uint8_t* data, size_t bytes, size_t *size) +{ + if (bytes < 1) + return NULL; + + if (data[0] < 252) + { + *size = data[0]; + return data + 1; + } + + if (bytes < 2) + return NULL; + *size = 4 * (uint16_t)data[1] + data[0]; + return data + 2; +} + +static const uint8_t* opus_parse_padding(const uint8_t* data, int len) +{ + int n; + int pad; + + pad = 0; + do + { + if (len <= 0) + return NULL; + + n = *data++; + len--; + + len -= n == 255 ? 254 : n; + pad += n == 255 ? 254 : n; + } while (n == 255); + + return data; +} + +/* + * Table 6-1 opus_access_unit syntax + + |Syntax |Number of |Identif| + | |bits |ier | + |opus_access_unit() { | | | + | if(nextbits(11)==0x3FF) { | | | + | opus_control_header() | | | + | | | | + | for(i=0; i> 4) & 0x01; + end_trim_flag = (prefix >> 3) & 0x01; + control_extension_flag = (prefix >> 2) & 0x01; + + au_size = data[2]; + for(i = 3; i < bytes && 0xff == data[i-1]; i++) + au_size += data[i]; + + if(i + (start_trim_flag ? 2 : 0) + (end_trim_flag ? 2 : 0) + (control_extension_flag ? 1 : 0) > bytes) + return NULL; + + if(start_trim_flag) + i += 2; + if(end_trim_flag) + i += 2; + if(control_extension_flag) + { + if(i + 1 + data[i] > bytes) + return NULL; + i += 1 + data[i]; + } + + if(i + au_size > bytes) + return NULL; + + *payload = au_size; + return data + i; + } + else + { + *payload = bytes; + return data; + } +} + +static int opus_parse_frames(const void* data, size_t len, int (*onframe)(uint8_t toc, const void* frame, size_t size), void* param) +{ + int i, r; + int vbr, count; + uint8_t toc; + size_t n[48]; + const uint8_t* p, *end; + + if (len < 1) + return -1; + + p = (const uint8_t*)data; + end = p + len; + + toc = *p++; + len -= 1; + switch (toc & 0x03) + { + case 0: // one frame + return onframe(toc, p, len - 1); + + case 1: // two CBR frames + if (1 == (len % 2)) + return -1; + + toc = toc & 0xFC; // convert to one frame + for (i = 0; i < 2; i++) + { + r = onframe(toc, p, len / 2); + if (0 != r) + return r; + } + return 0; + + case 2: // two VBR frames + p = opus_parse_size(p, len, &n[0]); + if (!p || n[0] < 0 || p + n[0] > end) + return -1; + + toc = toc & 0xFC; // convert to one frame + r = onframe(toc, p, n[0]); + if (0 != r) + return r; + + // frame 2 + p += n[0]; + return onframe(toc, p, end - p); + + default: // multiple CBR/VBR frames (from 0 to 120ms) + if (len < 1) + return -1; + + len--; + count = *p & 0x3F; // bits of frames length (0-5) + vbr = *p & 0x80; + if (*p++ & 0x40) // padding + { + p = opus_parse_padding(p, (int)len); + if (!p) + return -1; + } + + toc = toc & 0xFC; // convert to one frame + + if (vbr) + { + for (i = 0; i < count - 1; i++) + { + p = opus_parse_size(p, end - p, &n[i]); + if (!p || n[i] < 0 || p + n[i] > end) + return -1; + } + + /* Because it's not encoded explicitly, it's possible the size of the + last packet (or all the packets, for the CBR case) is larger than + 1275. Reject them here.*/ + if (end - p > 1275) + return -1; + n[i] = end - p; // last frame + + for (i = 0; i < count; i++) + { + r = onframe(toc, p, n[i]); + if (0 != r) + return r; + + p += n[i]; + } + } + else + { + n[0] = (end - p) / count; + if (p + n[0] * count != end) + return -1; + + for (i = 0; i < count; i++) + { + r = onframe(toc, p, n[0]); + if (0 != r) + return r; + + p += n[0]; + } + } + } + + return 0; +} + +int opus_packet_getframes(const void* data, size_t len, int (*onframe)(uint8_t toc, const void* frame, size_t size), void* param) +{ + int r; + size_t payload; + const uint8_t* p, *end; + + p = (const uint8_t*)data; + end = p + len; + + while(p < end) + { + p = opus_ts_header(p, end - p, &payload); + if(!p) + return -1; + assert(p + payload <= end); + + r = opus_parse_frames(p, payload, onframe, param); + if(r < 0) + return r; + + p += payload; + } + + return 0; +} + +#if defined(DEBUG) || defined(_DEBUG) +static int opus_onframe(uint8_t toc, const void* frame, size_t size) +{ + (void)toc, frame, size; + return 0; +} + +static void opus_packet_getframes_test(void) +{ + const uint8_t data[] = { 0x7F ,0xF0 ,0xF1 ,0x00 ,0x78 ,0xFC ,0x6F ,0xE9 ,0x04 ,0x92 ,0x8B ,0x99 ,0xEF ,0x20 ,0x00 ,0x20 ,0x58 ,0x7E ,0x2E ,0x82 ,0xC6 ,0xCC ,0x27 ,0x92 ,0x56 ,0x45 ,0xA7 ,0x5C ,0xDD ,0xAB ,0x41 ,0x1F ,0xD0 ,0x4A ,0x49 ,0xBB ,0xEA ,0xC2 ,0x1F ,0xD5 ,0x2A ,0x67 ,0xD2 ,0xF4 ,0x3F ,0x9E ,0xF4 ,0x52 ,0x38 ,0x41 ,0xBE ,0x55 ,0x4C ,0xFB ,0xD7 ,0x18 ,0xF1 ,0x93 ,0x26 ,0x36 ,0x46 ,0x01 ,0x41 ,0x85 ,0x7E ,0xAD ,0xB0 ,0x37 ,0x4B ,0xB7 ,0x15 ,0xB1 ,0x4C ,0x81 ,0x05 ,0x99 ,0xF8 ,0xE1 ,0xB6 ,0x54 }; + opus_packet_getframes(data, sizeof(data), opus_onframe, NULL); +} + +void opus_head_test(void) +{ + uint8_t data[29]; + const uint8_t src[] = { 0x4f, 0x70, 0x75, 0x73, 0x48, 0x65, 0x61, 0x64, 0x01, 0x02, 0x78, 0x00, 0x80, 0xbb, 0x00, 0x00, 0x00, 0x00, 0x00 }; + + struct opus_head_t opus; + assert(sizeof(src) == opus_head_load(src, sizeof(src), &opus)); + assert(1 == opus.version && 2 == opus.channels && 120 == opus.pre_skip && 48000 == opus.input_sample_rate && 0 == opus.output_gain); + assert(0 == opus.channel_mapping_family && 1 == opus.stream_count && 1 == opus.coupled_count); + assert(0 == memcmp(opus_channel_map[opus.channels-1], opus.channel_mapping, 8)); + assert(sizeof(src) == opus_head_save(&opus, data, sizeof(data))); + assert(0 == memcmp(src, data, sizeof(src))); + + opus_packet_getframes_test(); +} +#endif diff --git a/MediaServer/libflv/source/riff-acm.c b/MediaServer/libflv/source/riff-acm.c new file mode 100644 index 0000000..4297271 --- /dev/null +++ b/MediaServer/libflv/source/riff-acm.c @@ -0,0 +1,51 @@ +#include "riff-acm.h" + +int wave_format_load(const uint8_t* data, int bytes, struct wave_format_t* wav) +{ + if (bytes < 18) + return -1; + + // little endian + wav->wFormatTag = data[0] | ((uint16_t)data[1] << 8); + wav->nChannels = data[2] | ((uint16_t)data[3] << 8); + wav->nSamplesPerSec = ((uint32_t)data[4] << 0) | ((uint32_t)data[5] << 8) | ((uint32_t)data[6] << 16) | ((uint32_t)data[7] << 24); + wav->nAvgBytesPerSec = ((uint32_t)data[8] << 0) | ((uint32_t)data[9] << 8) | ((uint32_t)data[10] << 16) | ((uint32_t)data[11] << 24); + wav->nBlockAlign = data[12] | ((uint16_t)data[13] << 8); + wav->wBitsPerSample = data[14] | ((uint16_t)data[15] << 8); + wav->cbSize = data[16] | ((uint16_t)data[17] << 8); + + if (18 + wav->cbSize > bytes) + return -1; + + return 18 + wav->cbSize; +} + +int wave_format_save(const struct wave_format_t* wav, uint8_t* data, int bytes) +{ + if (bytes < wav->cbSize) + return -1; + + // little endian + data[0] = (uint8_t)wav->wFormatTag; + data[1] = (uint8_t)(wav->wFormatTag >> 8); + data[2] = (uint8_t)wav->nChannels; + data[3] = (uint8_t)(wav->nChannels >> 8); + data[4] = (uint8_t)wav->nSamplesPerSec; + data[5] = (uint8_t)(wav->nSamplesPerSec >> 8); + data[6] = (uint8_t)(wav->nSamplesPerSec >> 16); + data[7] = (uint8_t)(wav->nSamplesPerSec >> 24); + data[8] = (uint8_t)wav->nAvgBytesPerSec; + data[9] = (uint8_t)(wav->nAvgBytesPerSec >> 8); + data[10] = (uint8_t)(wav->nAvgBytesPerSec >> 16); + data[11] = (uint8_t)(wav->nAvgBytesPerSec >> 24); + data[12] = (uint8_t)wav->nBlockAlign; + data[13] = (uint8_t)(wav->nBlockAlign >> 8); + data[14] = (uint8_t)wav->wBitsPerSample; + data[15] = (uint8_t)(wav->wBitsPerSample >> 8); + data[16] = (uint8_t)wav->cbSize; + data[17] = (uint8_t)(wav->cbSize >> 8); + + //if(wav->cbSize > 0) + // memcpy(data + 18, wav->extra, wav->cbSize); + return wav->cbSize + 18; +} diff --git a/MediaServer/libflv/source/vvc-annexbtomp4.c b/MediaServer/libflv/source/vvc-annexbtomp4.c new file mode 100644 index 0000000..688edb4 --- /dev/null +++ b/MediaServer/libflv/source/vvc-annexbtomp4.c @@ -0,0 +1,433 @@ +#include "mpeg4-vvc.h" +#include "mpeg4-avc.h" +#include +#include +#include + +#define H266_NAL_IDR_W_RADL 7 +#define H266_NAL_RSV_IRAP 11 +#define H266_NAL_OPI 12 +#define H266_NAL_DCI 13 +#define H266_NAL_VPS 14 +#define H266_NAL_SPS 15 +#define H266_NAL_PPS 16 +#define H266_NAL_PREFIX_APS 17 +#define H266_NAL_SUFFIX_APS 18 +#define H266_NAL_PH 19 +#define H266_NAL_AUD 20 +#define H266_NAL_PREFIX_SEI 23 +#define H266_NAL_SUFFIX_SEI 24 + +#define MAX(x, y) ((x) > (y) ? (x) : (y)) + +#define BIT(ptr, off) (((ptr)[(off) / 8] >> (7 - ((off) % 8))) & 0x01) + +struct h266_annexbtomp4_handle_t +{ + struct mpeg4_vvc_t* vvc; + int errcode; + int* update; // avc sps/pps update flags + int* vcl; + + uint8_t* out; + size_t bytes; + size_t capacity; +}; + +uint8_t mpeg4_h264_read_ue(const uint8_t* data, size_t bytes, size_t* offset); + +static size_t vvc_rbsp_decode(const uint8_t* nalu, size_t bytes, uint8_t* sodb, size_t len) +{ + size_t i, j; + const size_t max_sps_luma_bit_depth_offset = 256; + for (j = i = 0; i < bytes && j < len && i < max_sps_luma_bit_depth_offset; i++) + { + if (i + 2 < bytes && 0 == nalu[i] && 0 == nalu[i + 1] && 0x03 == nalu[i + 2]) + { + sodb[j++] = nalu[i]; + sodb[j++] = nalu[i + 1]; + i += 2; + } + else + { + sodb[j++] = nalu[i]; + } + } + return j; +} + +static uint8_t vvc_vps_id(const uint8_t* rbsp, size_t bytes, struct mpeg4_vvc_t* vvc, uint8_t* ptr, size_t len) +{ + size_t sodb; + uint8_t vps; + uint8_t vps_max_layers_minus1; + uint8_t vps_max_sub_layers_minus1; + + sodb = vvc_rbsp_decode(rbsp, bytes, ptr, len); + if (sodb < 4) + return 0xFF; + + vps = ptr[2] >> 4; // 2-nalu type + vps_max_layers_minus1 = (ptr[3] >> 2) & 0x3F; + vps_max_sub_layers_minus1 = ((ptr[3] & 0x3) << 2) | ((ptr[4] >> 7) & 0x01); + + return vps; +} + +static uint8_t vvc_sps_id(const uint8_t* rbsp, size_t bytes, struct mpeg4_vvc_t* vvc, uint8_t* ptr, size_t len, uint8_t* vps) +{ + size_t sodb; + uint8_t sps; + uint8_t sps_max_sub_layers_minus1; + + sodb = vvc_rbsp_decode(rbsp, bytes, ptr, len); + if (sodb < 12 + 3) + return 0xFF; + + sps = (ptr[2] >> 4) & 0x0F; // 2-nalu type + *vps = ptr[2] & 0x0F; + sps_max_sub_layers_minus1 = (ptr[3] >> 5) & 0x07; + vvc->chroma_format_idc = (ptr[3] >> 3) & 0x03; + + return sps; +} + +static uint8_t vvc_pps_id(const uint8_t* rbsp, size_t bytes, struct mpeg4_vvc_t* vvc, uint8_t* ptr, size_t len, uint8_t* sps) +{ + uint8_t pps; + size_t sodb; + size_t n = 2 * 8; // 2-nalu type + sodb = vvc_rbsp_decode(rbsp, bytes, ptr, len); + if (sodb < 12) + return 0xFF; (void)vvc; + pps = (ptr[2] >> 2) & 0x3F; // 2-nalu type + *sps = ((ptr[2] & 0x03) << 2) | ((ptr[3] >> 6) & 0x03); + + n = 11; + vvc->max_picture_width = mpeg4_h264_read_ue(ptr, sodb, &n); // pic_width_in_luma_samples + vvc->max_picture_height = mpeg4_h264_read_ue(ptr, sodb, &n); // pic_height_in_luma_samples + return pps; +} + +static void mpeg4_vvc_remove(struct mpeg4_vvc_t* vvc, uint8_t* ptr, size_t bytes, const uint8_t* end) +{ + uint8_t i; + assert(ptr >= vvc->data && ptr + bytes <= end && end <= vvc->data + sizeof(vvc->data)); + memmove(ptr, ptr + bytes, end - ptr - bytes); + + for (i = 0; i < vvc->numOfArrays; i++) + { + if (vvc->nalu[i].data > ptr) + vvc->nalu[i].data -= bytes; + } +} + +static int mpeg4_vvc_update2(struct mpeg4_vvc_t* vvc, int i, const uint8_t* nalu, size_t bytes) +{ + if (bytes == vvc->nalu[i].bytes && 0 == memcmp(nalu, vvc->nalu[i].data, bytes)) + return 0; // do nothing + + if (bytes > vvc->nalu[i].bytes && vvc->off + (bytes - vvc->nalu[i].bytes) > sizeof(vvc->data)) + { + assert(0); + return -1; // too big + } + + mpeg4_vvc_remove(vvc, vvc->nalu[i].data, vvc->nalu[i].bytes, vvc->data + vvc->off); + vvc->off -= vvc->nalu[i].bytes; + + vvc->nalu[i].data = vvc->data + vvc->off; + vvc->nalu[i].bytes = (uint16_t)bytes; + memcpy(vvc->nalu[i].data, nalu, bytes); + vvc->off += bytes; + return 1; +} + +static int mpeg4_vvc_add(struct mpeg4_vvc_t* vvc, uint8_t type, const uint8_t* nalu, size_t bytes) +{ + // copy new + assert(vvc->numOfArrays < sizeof(vvc->nalu) / sizeof(vvc->nalu[0])); + if (vvc->numOfArrays >= sizeof(vvc->nalu) / sizeof(vvc->nalu[0]) + || vvc->off + bytes > sizeof(vvc->data)) + { + assert(0); + return -1; + } + + vvc->nalu[vvc->numOfArrays].type = type; + vvc->nalu[vvc->numOfArrays].bytes = (uint16_t)bytes; + vvc->nalu[vvc->numOfArrays].array_completeness = 1; + vvc->nalu[vvc->numOfArrays].data = vvc->data + vvc->off; + memcpy(vvc->nalu[vvc->numOfArrays].data, nalu, bytes); + vvc->off += bytes; + ++vvc->numOfArrays; + return 1; +} + +static int h266_opi_copy(struct mpeg4_vvc_t* vvc, const uint8_t* nalu, size_t bytes) +{ + int i; + for (i = 0; i < vvc->numOfArrays; i++) + { + if (H266_NAL_OPI == vvc->nalu[i].type) + return mpeg4_vvc_update2(vvc, i, nalu, bytes); + } + + return mpeg4_vvc_add(vvc, H266_NAL_OPI, nalu, bytes); +} + +static int h266_dci_copy(struct mpeg4_vvc_t* vvc, const uint8_t* nalu, size_t bytes) +{ + int i; + for (i = 0; i < vvc->numOfArrays; i++) + { + if (H266_NAL_DCI == vvc->nalu[i].type) + return mpeg4_vvc_update2(vvc, i, nalu, bytes); + } + + return mpeg4_vvc_add(vvc, H266_NAL_DCI, nalu, bytes); +} + +static int h266_vps_copy(struct mpeg4_vvc_t* vvc, const uint8_t* nalu, size_t bytes) +{ + int i; + uint8_t vpsid; + + if (bytes < 3) + { + assert(0); + return -1; // invalid length + } + + vpsid = vvc_vps_id(nalu, bytes, vvc, vvc->data + vvc->off, sizeof(vvc->data) - vvc->off); + for (i = 0; i < vvc->numOfArrays; i++) + { + if (H266_NAL_VPS == vvc->nalu[i].type && vpsid == vvc_vps_id(vvc->nalu[i].data, vvc->nalu[i].bytes, vvc, vvc->data + vvc->off, sizeof(vvc->data) - vvc->off)) + return mpeg4_vvc_update2(vvc, i, nalu, bytes); + } + + return mpeg4_vvc_add(vvc, H266_NAL_VPS, nalu, bytes); +} + +static int h266_sps_copy(struct mpeg4_vvc_t* vvc, const uint8_t* nalu, size_t bytes) +{ + int i; + uint8_t spsid; + uint8_t vpsid, vpsid2; + + if (bytes < 13 + 2) + { + assert(0); + return -1; // invalid length + } + + spsid = vvc_sps_id(nalu, bytes, vvc, vvc->data + vvc->off, sizeof(vvc->data) - vvc->off, &vpsid); + for (i = 0; i < vvc->numOfArrays; i++) + { + if (H266_NAL_SPS == vvc->nalu[i].type && spsid == vvc_sps_id(vvc->nalu[i].data, vvc->nalu[i].bytes, vvc, vvc->data + vvc->off, sizeof(vvc->data) - vvc->off, &vpsid2) && vpsid == vpsid2) + return mpeg4_vvc_update2(vvc, i, nalu, bytes); + } + + return mpeg4_vvc_add(vvc, H266_NAL_SPS, nalu, bytes); +} + +static int h266_pps_copy(struct mpeg4_vvc_t* vvc, const uint8_t* nalu, size_t bytes) +{ + int i; + uint8_t ppsid; + uint8_t spsid, spsid2; + + if (bytes < 1 + 2) + { + assert(0); + return -1; // invalid length + } + + ppsid = vvc_pps_id(nalu, bytes, vvc, vvc->data + vvc->off, sizeof(vvc->data) - vvc->off, &spsid); + for (i = 0; i < vvc->numOfArrays; i++) + { + if (H266_NAL_PPS == vvc->nalu[i].type && ppsid == vvc_pps_id(vvc->nalu[i].data, vvc->nalu[i].bytes, vvc, vvc->data + vvc->off, sizeof(vvc->data) - vvc->off, &spsid2) && spsid == spsid2) + return mpeg4_vvc_update2(vvc, i, nalu, bytes); + } + + return mpeg4_vvc_add(vvc, H266_NAL_PPS, nalu, bytes); +} + +static int h266_sei_clear(struct mpeg4_vvc_t* vvc) +{ + int i; + for (i = 0; i < vvc->numOfArrays; i++) + { + if (H266_NAL_PREFIX_SEI == vvc->nalu[i].type || H266_NAL_SUFFIX_SEI == vvc->nalu[i].type) + { + mpeg4_vvc_remove(vvc, vvc->nalu[i].data, vvc->nalu[i].bytes, vvc->data + vvc->off); + vvc->off -= vvc->nalu[i].bytes; + if (i + 1 < vvc->numOfArrays) + memmove(vvc->nalu + i, vvc->nalu + i + 1, sizeof(vvc->nalu[0]) * (vvc->numOfArrays - i - 1)); + --vvc->numOfArrays; + --i; + } + } + return 0; +} + +int mpeg4_vvc_update(struct mpeg4_vvc_t* vvc, const uint8_t* nalu, size_t bytes) +{ + int r; + + switch ((nalu[1] >> 3) & 0x1f) + { + case H266_NAL_OPI: + r = h266_opi_copy(vvc, nalu, bytes); + break; + + case H266_NAL_DCI: + r = h266_dci_copy(vvc, nalu, bytes); + break; + + case H266_NAL_VPS: + h266_sei_clear(vvc); // remove all prefix/suffix sei + r = h266_vps_copy(vvc, nalu, bytes); + break; + + case H266_NAL_SPS: + r = h266_sps_copy(vvc, nalu, bytes); + break; + + case H266_NAL_PPS: + r = h266_pps_copy(vvc, nalu, bytes); + break; + +#if defined(H266_FILTER_SEI) + case H266_NAL_PREFIX_SEI: + r = mpeg4_vvc_add(vvc, H266_NAL_SEI_PREFIX, nalu, bytes); + break; + + case H266_NAL_SUFFIX_SEI: + r = mpeg4_vvc_add(vvc, H266_NAL_SEI_SUFFIX, nalu, bytes); + break; +#endif + + default: + r = 0; + break; + } + + return r; +} + +static void vvc_handler(void* param, const uint8_t* nalu, size_t bytes) +{ + int r; + uint8_t nalutype; + struct h266_annexbtomp4_handle_t* mp4; + mp4 = (struct h266_annexbtomp4_handle_t*)param; + + if (bytes < 2) + { + assert(0); + mp4->errcode = -EINVAL; + return; + } + + nalutype = (nalu[1] >> 3) & 0x1f; +#if defined(H2645_FILTER_AUD) + if (H266_NAL_AUD == nalutype) + return; // ignore AUD +#endif + + r = mpeg4_vvc_update(mp4->vvc, nalu, bytes); + if (1 == r && mp4->update) + *mp4->update = 1; + else if (r < 0) + mp4->errcode = r; + + // IRAP-1, B/P-2, other-0 + if (mp4->vcl && nalutype < H266_NAL_OPI) + *mp4->vcl = H266_NAL_IDR_W_RADL <= nalutype && nalutype <= H266_NAL_RSV_IRAP ? 1 : 2; + + if (mp4->capacity >= mp4->bytes + bytes + 4) + { + mp4->out[mp4->bytes + 0] = (uint8_t)((bytes >> 24) & 0xFF); + mp4->out[mp4->bytes + 1] = (uint8_t)((bytes >> 16) & 0xFF); + mp4->out[mp4->bytes + 2] = (uint8_t)((bytes >> 8) & 0xFF); + mp4->out[mp4->bytes + 3] = (uint8_t)((bytes >> 0) & 0xFF); + memmove(mp4->out + mp4->bytes + 4, nalu, bytes); + mp4->bytes += bytes + 4; + } + else + { + mp4->errcode = -1; + } +} + +int h266_annexbtomp4(struct mpeg4_vvc_t* vvc, const void* data, size_t bytes, void* out, size_t size, int* vcl, int* update) +{ + struct h266_annexbtomp4_handle_t h; + memset(&h, 0, sizeof(h)); + h.vvc = vvc; + h.vcl = vcl; + h.update = update; + h.out = (uint8_t*)out; + h.capacity = size; + if (vcl) *vcl = 0; + if (update) *update = 0; + + // vvc->numTemporalLayers = 0; + // vvc->temporalIdNested = 0; + // vvc->min_spatial_segmentation_idc = 0; + // vvc->general_profile_compatibility_flags = 0xffffffff; + // vvc->general_constraint_indicator_flags = 0xffffffffffULL; + // vvc->chromaFormat = 1; // 4:2:0 + + mpeg4_h264_annexb_nalu((const uint8_t*)data, bytes, vvc_handler, &h); + vvc->lengthSizeMinusOne = 3; // 4 bytes + return 0 == h.errcode ? (int)h.bytes : 0; +} + +int h266_is_new_access_unit(const uint8_t* nalu, size_t bytes) +{ + uint8_t nal_type; + uint8_t nuh_layer_id; + + if (bytes < 3) + return 0; + + nal_type = (nalu[1] >> 3) & 0x1f; + nuh_layer_id = nalu[0] & 0x3F; + + // 7.4.2.4.3 Order of PUs and their association to AUs + if (H266_NAL_AUD == nal_type || H266_NAL_OPI == nal_type || H266_NAL_DCI == nal_type || H266_NAL_VPS == nal_type || H266_NAL_SPS == nal_type || H266_NAL_PPS == nal_type || + (nuh_layer_id == 0 && (H266_NAL_PREFIX_APS == nal_type || H266_NAL_PH == nal_type || H266_NAL_PREFIX_SEI == nal_type || + 26 == nal_type || (28 <= nal_type && nal_type <= 29)))) + return 1; + + // 7.4.2.4.4 Order of NAL units and coded pictures and their association to PUs + if (nal_type < H266_NAL_OPI) + { + //sh_picture_header_in_slice_header_flag == 1 + return (nalu[2] & 0x80) ? 1 : 0; + } + + return 0; +} + +#if defined(_DEBUG) || defined(DEBUG) +void vvc_annexbtomp4_test(void) +{ + const uint8_t vps[] = { 0x40, 0x01, 0x0c, 0x01, 0xff, 0xff, 0x01, 0x60, 0x00, 0x00, 0x03, 0x00, 0x80, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x78, 0x9d, 0xc0, 0x90 }; + const uint8_t sps[] = { 0x42, 0x01, 0x01, 0x01, 0x60, 0x00, 0x00, 0x03, 0x00, 0x80, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x78, 0xa0, 0x03, 0xc0, 0x80, 0x32, 0x16, 0x59, 0xde, 0x49, 0x1b, 0x6b, 0x80, 0x40, 0x00, 0x00, 0xfa, 0x00, 0x00, 0x17, 0x70, 0x02 }; + const uint8_t pps[] = { 0x44, 0x01, 0xc1, 0x73, 0xd1, 0x89 }; + const uint8_t annexb[] = { 0x00, 0x00, 0x00, 0x01, 0x4e, 0x01, 0x06, 0x01, 0xd0, 0x80, 0x00, 0x00, 0x00, 0x01, 0x40, 0x01, 0x0c, 0x01, 0xff, 0xff, 0x01, 0x60, 0x00, 0x00, 0x03, 0x00, 0x80, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x78, 0x9d, 0xc0, 0x90, 0x00, 0x00, 0x00, 0x01, 0x42, 0x01, 0x01, 0x01, 0x60, 0x00, 0x00, 0x03, 0x00, 0x80, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x78, 0xa0, 0x03, 0xc0, 0x80, 0x32, 0x16, 0x59, 0xde, 0x49, 0x1b, 0x6b, 0x80, 0x40, 0x00, 0x00, 0xfa, 0x00, 0x00, 0x17, 0x70, 0x02, 0x00, 0x00, 0x00, 0x01, 0x44, 0x01, 0xc1, 0x73, 0xd1, 0x89 }; + uint8_t output[512]; + int vcl, update; + + struct mpeg4_vvc_t vvc; + memset(&vvc, 0, sizeof(vvc)); + assert(h266_annexbtomp4(&vvc, annexb, sizeof(annexb), output, sizeof(output), &vcl, &update) > 0); + assert(3 == vvc.numOfArrays && vcl == 0 && update == 1); + assert(vvc.nalu[0].bytes == sizeof(vps) && 0 == memcmp(vvc.nalu[0].data, vps, sizeof(vps))); + assert(vvc.nalu[1].bytes == sizeof(sps) && 0 == memcmp(vvc.nalu[1].data, sps, sizeof(sps))); + assert(vvc.nalu[2].bytes == sizeof(pps) && 0 == memcmp(vvc.nalu[2].data, pps, sizeof(pps))); +} +#endif diff --git a/MediaServer/libflv/source/vvc-mp4toannexb.c b/MediaServer/libflv/source/vvc-mp4toannexb.c new file mode 100644 index 0000000..2c289f2 --- /dev/null +++ b/MediaServer/libflv/source/vvc-mp4toannexb.c @@ -0,0 +1,138 @@ +#include "mpeg4-vvc.h" +#include "mpeg4-avc.h" +#include +#include +#include +#include +#include + +#define H266_NAL_IDR_W_RADL 7 +#define H266_NAL_RSV_IRAP 11 +#define H266_NAL_OPI 12 +#define H266_NAL_DCI 13 +#define H266_NAL_VPS 14 +#define H266_NAL_SPS 15 +#define H266_NAL_PPS 16 +#define H266_NAL_AUD 20 +#define H266_NAL_PREFIX_SEI 23 +#define H266_NAL_SUFFIX_SEI 24 + +struct h266_mp4toannexb_handle_t +{ + const struct mpeg4_vvc_t* vvc; + int vps_sps_pps_flag; + int errcode; + + uint8_t* out; + size_t bytes; + size_t capacity; +}; + +static int h266_vps_sps_pps_size(const struct mpeg4_vvc_t* vvc) +{ + int i, n = 0; + for (i = 0; i < vvc->numOfArrays; i++) + n += vvc->nalu[i].bytes + 4; + return n; +} + +static void h266_mp4toannexb_handler(void* param, const uint8_t* nalu, size_t bytes) +{ + int n; + uint8_t irap, nalu_type; + const uint8_t h266_start_code[] = { 0x00, 0x00, 0x00, 0x01 }; + struct h266_mp4toannexb_handle_t* mp4; + mp4 = (struct h266_mp4toannexb_handle_t*)param; + + if (bytes < 2) + { + assert(0); + mp4->errcode = -EINVAL; + return; + } + + nalu_type = (nalu[1] >> 3) & 0x1F; +#if defined(H2645_FILTER_AUD) + if (H266_NAL_AUD == nalu_type) + continue; // ignore AUD +#endif + + if (H266_NAL_OPI == nalu_type || H266_NAL_DCI == nalu_type || H266_NAL_VPS == nalu_type || H266_NAL_SPS == nalu_type || H266_NAL_PPS == nalu_type) + mp4->vps_sps_pps_flag = 1; + + irap = H266_NAL_IDR_W_RADL <= nalu_type && nalu_type <= H266_NAL_RSV_IRAP; + if (irap && 0 == mp4->vps_sps_pps_flag) + { + // insert VPS/SPS/PPS before IDR frame + if (mp4->bytes > 0) + { + // write sps/pps at first + n = h266_vps_sps_pps_size(mp4->vvc); + if (n + mp4->bytes > mp4->capacity) + { + mp4->errcode = -E2BIG; + return; + } + memmove(mp4->out + n, mp4->out, mp4->bytes); + } + + n = mpeg4_vvc_to_nalu(mp4->vvc, mp4->out, mp4->capacity); + if (n <= 0) + { + mp4->errcode = 0 == n ? -EINVAL : n; + return; + } + mp4->bytes += n; + mp4->vps_sps_pps_flag = 1; + } + + if (mp4->bytes + bytes + sizeof(h266_start_code) > mp4->capacity) + { + mp4->errcode = -E2BIG; + return; + } + + memcpy(mp4->out + mp4->bytes, h266_start_code, sizeof(h266_start_code)); + memcpy(mp4->out + mp4->bytes + sizeof(h266_start_code), nalu, bytes); + mp4->bytes += sizeof(h266_start_code) + bytes; +} + +int h266_mp4toannexb(const struct mpeg4_vvc_t* vvc, const void* data, size_t bytes, void* out, size_t size) +{ + int i, n; + const uint8_t* src, * end; + struct h266_mp4toannexb_handle_t h; + + memset(&h, 0, sizeof(h)); + h.vvc = vvc; + h.out = (uint8_t*)out; + h.capacity = size; + + end = (uint8_t*)data + bytes; + for (src = (uint8_t*)data; src + vvc->lengthSizeMinusOne + 1 < end; src += n) + { + for (n = i = 0; i < vvc->lengthSizeMinusOne + 1; i++) + n = (n << 8) + ((uint8_t*)src)[i]; + + // fix 0x00 00 00 01 => flv nalu size + if (0 == vvc->lengthSizeMinusOne || (1 == n && (2 == vvc->lengthSizeMinusOne || 3 == vvc->lengthSizeMinusOne))) + { + //n = (int)(end - src) - avc->nalu; + mpeg4_h264_annexb_nalu(src, end - src, h266_mp4toannexb_handler, &h); + src = end; + break; + } + + src += vvc->lengthSizeMinusOne + 1; + if (n < 1 || src + n > end) + { + assert(0); + return -EINVAL; + } + + h266_mp4toannexb_handler(&h, src, n); + } + + assert(src == end); + return 0 == h.errcode ? (int)h.bytes : 0; +} diff --git a/MediaServer/libflv/source/webm-vpx.c b/MediaServer/libflv/source/webm-vpx.c new file mode 100644 index 0000000..723a56e --- /dev/null +++ b/MediaServer/libflv/source/webm-vpx.c @@ -0,0 +1,171 @@ +#include "webm-vpx.h" +#include "mpeg4-bits.h" +#include +#include +#include + +enum { + WEBM_VP_LEVEL_1 = 10, + WEBM_VP_LEVEL_1_1 = 11, + WEBM_VP_LEVEL_2 = 20, + WEBM_VP_LEVEL_2_1 = 21, + WEBM_VP_LEVEL_3 = 30, + WEBM_VP_LEVEL_3_1 = 31, + WEBM_VP_LEVEL_4 = 40, + WEBM_VP_LEVEL_4_1 = 41, + WEBM_VP_LEVEL_5 = 50, + WEBM_VP_LEVEL_5_1 = 51, + WEBM_VP_LEVEL_5_2 = 52, + WEBM_VP_LEVEL_6 = 60, + WEBM_VP_LEVEL_6_1 = 61, + WEBM_VP_LEVEL_6_2 = 62, +}; + +/* +aligned (8) class VPCodecConfigurationRecord { + unsigned int (8) profile; + unsigned int (8) level; + unsigned int (4) bitDepth; + unsigned int (3) chromaSubsampling; + unsigned int (1) videoFullRangeFlag; + unsigned int (8) colourPrimaries; + unsigned int (8) transferCharacteristics; + unsigned int (8) matrixCoefficients; + unsigned int (16) codecIntializationDataSize; + unsigned int (8)[] codecIntializationData; +} +*/ + +int webm_vpx_codec_configuration_record_load(const uint8_t* data, size_t bytes, struct webm_vpx_t* vpx) +{ + if (bytes < 8) + return -1; + + vpx->profile = data[0]; + vpx->level = data[1]; + vpx->bit_depth = (data[2] >> 4) & 0x0F; + vpx->chroma_subsampling = (data[2] >> 1) & 0x07; + vpx->video_full_range_flag = data[2] & 0x01; + vpx->colour_primaries = data[3]; + vpx->transfer_characteristics = data[4]; + vpx->matrix_coefficients = data[5]; + vpx->codec_intialization_data_size = (((uint16_t)data[6]) << 8) | data[7]; + assert(0 == vpx->codec_intialization_data_size); + return 8; +} + +int webm_vpx_codec_configuration_record_save(const struct webm_vpx_t* vpx, uint8_t* data, size_t bytes) +{ + if (bytes < 8 + (size_t)vpx->codec_intialization_data_size) + return 0; // don't have enough memory + + data[0] = vpx->profile; + data[1] = vpx->level; + data[2] = (vpx->bit_depth << 4) | ((vpx->chroma_subsampling & 0x07) << 1) | (vpx->video_full_range_flag & 0x01); + data[3] = vpx->colour_primaries; + data[4] = vpx->transfer_characteristics; + data[5] = vpx->matrix_coefficients; + data[6] = (uint8_t)(vpx->codec_intialization_data_size >> 8); + data[7] = (uint8_t)vpx->codec_intialization_data_size; + + if(vpx->codec_intialization_data_size > 0) + memcpy(data + 8, vpx->codec_intialization_data, vpx->codec_intialization_data_size); + return 8 + vpx->codec_intialization_data_size; +} + +// https://www.webmproject.org/vp9/mp4/ +// https://github.com/webmproject/vp9-dash +// https://www.rfc-editor.org/rfc/pdfrfc/rfc6386.txt.pdf +// https://storage.googleapis.com/downloads.webmproject.org/docs/vp9/vp9-bitstream-specification-v0.6-20160331-draft.pdf +int webm_vpx_codec_configuration_record_from_vp8(struct webm_vpx_t* vpx, int *width, int* height, const void* keyframe, size_t bytes) +{ + uint32_t tag; + const uint8_t* p; + const uint8_t startcode[] = { 0x9d, 0x01, 0x2a }; + + if (bytes < 10) + return -1; + + p = (const uint8_t*)keyframe; + + // 9.1. Uncompressed Data Chunk + tag = (uint32_t)p[0] | ((uint32_t)p[1] << 8) | ((uint32_t)p[2] << 16); + //key_frame = tag & 0x01; + //version = (tag >> 1) & 0x07; + //show_frame = (tag >> 4) & 0x1; + //first_part_size = (tag >> 5) & 0x7FFFF; + + if (0 != (tag & 0x01) || startcode[0] != p[3] || startcode[1] != p[4] || startcode[2] != p[5]) + return -1; // not key frame + + *width = ((uint16_t)(p[7] & 0x3F) << 8) | (uint16_t)(p[6]); // (2 bits Horizontal Scale << 14) | Width (14 bits) + *height = ((uint16_t)(p[9] & 0x3F) << 8) | (uint16_t)(p[8]); // (2 bits Vertical Scale << 14) | Height (14 bits) + + memset(vpx, 0, sizeof(*vpx)); + vpx->profile = (tag >> 1) & 0x03; + vpx->level = 31; + vpx->bit_depth = 8; + return 0; +} + +// https://storage.googleapis.com/downloads.webmproject.org/docs/vp9/vp9-bitstream-specification-v0.6-20160331-draft.pdf +int webm_vpx_codec_configuration_record_from_vp9(struct webm_vpx_t* vpx, int* width, int* height, const void* keyframe, size_t bytes) +{ + const uint8_t* p; + struct mpeg4_bits_t bits; + const uint8_t frame_sync_code[] = { 0x49, 0x83, 0x42 }; + + p = (const uint8_t*)keyframe; + if (bytes < 4 || frame_sync_code[0] != p[1] || frame_sync_code[1] != p[2] || frame_sync_code[2] != p[3]) + return -1; + + memset(vpx, 0, sizeof(*vpx)); + vpx->level = 31; + + // 6.2 Uncompressed header syntax + mpeg4_bits_init(&bits, (void*)keyframe, bytes); + mpeg4_bits_read_n(&bits, 2); // 2-frame_marker + vpx->profile = (uint8_t)(mpeg4_bits_read(&bits) | (mpeg4_bits_read(&bits) << 1)); // 2-profile_low_bit+profile_high_bit + mpeg4_bits_read_n(&bits, 4 + 24); // skip 4-bits + frame_sync_code + + // color_config() + if (vpx->profile >= 2) + vpx->bit_depth = (uint8_t)mpeg4_bits_read(&bits) ? 12 : 10; // 1-ten_or_twelve_bit + else + vpx->bit_depth = 8; + if (7 /*CS_RGB*/ != mpeg4_bits_read_n(&bits, 3)) // 3-color_space + { + vpx->video_full_range_flag = (uint8_t)mpeg4_bits_read(&bits); // color_range + if (1 == vpx->profile || 3 == vpx->profile) + { + vpx->chroma_subsampling = 3 - (uint8_t)mpeg4_bits_read_n(&bits, 2); // subsampling_x/subsampling_y + mpeg4_bits_read(&bits); // reserved_zero + } + } + else + { + if (1 == vpx->profile || 3 == vpx->profile) + mpeg4_bits_read(&bits); // reserved_zero + } + + // frame_size() + *width = (int)mpeg4_bits_read_n(&bits, 16) + 1; + *height = (int)mpeg4_bits_read_n(&bits, 16) + 1; + return mpeg4_bits_error(&bits) ? -1 : 0; +} + +#if defined(_DEBUG) || defined(DEBUG) +void webm_vpx_test(void) +{ + const unsigned char src[] = { + 0x00, 0x1f, 0x80, 0x02, 0x02, 0x02, 0x00, 0x00 + }; + unsigned char data[sizeof(src)]; + + struct webm_vpx_t vpx; + assert(sizeof(src) == webm_vpx_codec_configuration_record_load(src, sizeof(src), &vpx)); + assert(0 == vpx.profile && 31 == vpx.level && 8 == vpx.bit_depth && 0 == vpx.chroma_subsampling && 0 == vpx.video_full_range_flag); + assert(sizeof(src) == webm_vpx_codec_configuration_record_save(&vpx, data, sizeof(data))); + assert(0 == memcmp(src, data, sizeof(src))); +} +#endif diff --git a/MediaServer/libmov/include/fmp4-writer.h b/MediaServer/libmov/include/fmp4-writer.h new file mode 100644 index 0000000..3266976 --- /dev/null +++ b/MediaServer/libmov/include/fmp4-writer.h @@ -0,0 +1,49 @@ +#ifndef _fmp4_writer_h_ +#define _fmp4_writer_h_ + +#include +#include +#include "mov-buffer.h" + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct fmp4_writer_t fmp4_writer_t; + +/// @param[in] flags mov flags, such as: MOV_FLAG_SEGMENT, see more @mov-format.h +fmp4_writer_t* fmp4_writer_create(const struct mov_buffer_t *buffer, void* param, int flags); +void fmp4_writer_destroy(fmp4_writer_t* fmp4); + +/// @param[in] object MPEG-4 systems ObjectTypeIndication such as: MOV_OBJECT_H264, see more @mov-format.h +/// @param[in] extra_data AudioSpecificConfig/AVCDecoderConfigurationRecord/HEVCDecoderConfigurationRecord +/// @return >=0-track, <0-error +int fmp4_writer_add_audio(fmp4_writer_t* fmp4, uint8_t object, int channel_count, int bits_per_sample, int sample_rate, const void* extra_data, size_t extra_data_size); +int fmp4_writer_add_video(fmp4_writer_t* fmp4, uint8_t object, int width, int height, const void* extra_data, size_t extra_data_size); +int fmp4_writer_add_subtitle(fmp4_writer_t* fmp4, uint8_t object, const void* extra_data, size_t extra_data_size); + +/// Write audio/video stream +/// raw AAC data, don't include ADTS/AudioSpecificConfig +/// H.264/H.265 MP4 format, replace start code(0x00000001) with NALU size +/// @param[in] track return by mov_writer_add_audio/mov_writer_add_video +/// @param[in] data audio/video frame +/// @param[in] bytes buffer size +/// @param[in] pts timestamp in millisecond +/// @param[in] dts timestamp in millisecond +/// @param[in] flags MOV_AV_FLAG_XXX, such as: MOV_AV_FLAG_KEYFREAME, see more @mov-format.h +/// @return 0-ok, other-error +int fmp4_writer_write(fmp4_writer_t* fmp4, int track, const void* data, size_t bytes, int64_t pts, int64_t dts, int flags); + +/// Save data and open next segment +/// @return 0-ok, other-error +int fmp4_writer_save_segment(fmp4_writer_t* fmp4); + +/// Get init segment data(write FTYP, MOOV only) +/// WARNING: it caller duty to switch file/buffer context with fmp4_writer_write +/// @return 0-ok, other-error +int fmp4_writer_init_segment(fmp4_writer_t* fmp4); + +#ifdef __cplusplus +} +#endif +#endif /* !_fmp4_writer_h_ */ diff --git a/MediaServer/libmov/include/mov-atom.h b/MediaServer/libmov/include/mov-atom.h new file mode 100644 index 0000000..107a8c5 --- /dev/null +++ b/MediaServer/libmov/include/mov-atom.h @@ -0,0 +1,182 @@ +#ifndef _mov_atom_h_ +#define _mov_atom_h_ + +#include +#include + +#define N_BRAND 8 + +struct mov_ftyp_t +{ + uint32_t major_brand; + uint32_t minor_version; + + uint32_t compatible_brands[N_BRAND]; + int brands_count; +}; + +// A.4 Temporal structure of the media (p148) +// The movie, and each track, has a timescale. +// This defines a time axis which has a number of ticks per second +struct mov_mvhd_t +{ + // FullBox + uint32_t version : 8; + uint32_t flags : 24; + + uint32_t timescale; // time-scale for the entire presentation, the number of time units that pass in one second + uint64_t duration; // default UINT64_MAX(by timescale) + uint64_t creation_time; // seconds sine midnight, Jan. 1, 1904, UTC + uint64_t modification_time; // seconds sine midnight, Jan. 1, 1904, UTC + + uint32_t rate; + uint16_t volume; // fixed point 8.8 number, 1.0 (0x0100) is full volume + //uint16_t reserved; + //uint32_t reserved2[2]; + int32_t matrix[9]; // u,v,w + //int32_t pre_defined[6]; + uint32_t next_track_ID; +}; + +enum +{ + MOV_TKHD_FLAG_TRACK_ENABLE = 0x01, + MOV_TKHD_FLAG_TRACK_IN_MOVIE = 0x02, + MOV_TKHD_FLAG_TRACK_IN_PREVIEW = 0x04, +}; + +struct mov_tkhd_t +{ + // FullBox + uint32_t version : 8; + uint32_t flags : 24; // MOV_TKHD_FLAG_XXX + + uint32_t track_ID; // cannot be zero + uint64_t creation_time; // seconds sine midnight, Jan. 1, 1904, UTC + uint64_t modification_time; // seconds sine midnight, Jan. 1, 1904, UTC + uint64_t duration; // default UINT64_MAX(by Movie Header Box timescale) + //uint32_t reserved; + + //uint32_t reserved2[2]; + int16_t layer; + int16_t alternate_group; + int16_t volume; // fixed point 8.8 number, 1.0 (0x0100) is full volume + //uint16_t reserved; + int32_t matrix[9]; // u,v,w + uint32_t width; // fixed-point 16.16 values + uint32_t height; // fixed-point 16.16 values +}; + +struct mov_mdhd_t +{ + // FullBox + uint32_t version : 8; + uint32_t flags : 24; + + uint32_t timescale; // second + uint64_t duration; // default UINT64_MAX(by timescale) + uint64_t creation_time; // seconds sine midnight, Jan. 1, 1904, UTC + uint64_t modification_time; // seconds sine midnight, Jan. 1, 1904, UTC + + uint32_t pad : 1; + uint32_t language : 15; + uint32_t pre_defined : 16; +}; + +struct mov_sample_entry_t +{ + uint16_t data_reference_index; // ref [dref] Data Reference Boxes + uint8_t object_type_indication; // H.264/AAC MOV_OBJECT_XXX (DecoderConfigDescriptor) + uint8_t stream_type; // MP4_STREAM_XXX + uint8_t* extra_data; // H.264 sps/pps + int extra_data_size; + + union + { + struct mov_bitrate_t + { + uint32_t bufferSizeDB; + uint32_t maxBitrate; + uint32_t avgBitrate; + } bitrate; + + //struct mov_uri_t + //{ + // char uri[256]; + //} uri; + + // visual + struct mov_visual_sample_t + { + uint16_t width; + uint16_t height; + uint32_t horizresolution; // 0x00480000 - 72dpi + uint32_t vertresolution; // 0x00480000 - 72dpi + uint16_t frame_count; // default 1 + uint16_t depth; // 0x0018 + + struct mov_pixel_aspect_ratio_t + { + uint32_t h_spacing; + uint32_t v_spacing; + } pasp; + } visual; + + struct mov_audio_sample_t + { + uint16_t channelcount; // default 2 + uint16_t samplesize; // default 16 + uint32_t samplerate; // { default samplerate of media } << 16 + } audio; + } u; +}; + +struct mov_stsd_t +{ + struct mov_sample_entry_t *current; // current entry, read only + struct mov_sample_entry_t *entries; + uint32_t entry_count; +}; + +struct mov_stts_t +{ + uint32_t sample_count; + uint32_t sample_delta; // in the time-scale of the media +}; + +struct mov_stsc_t +{ + uint32_t first_chunk; + uint32_t samples_per_chunk; + uint32_t sample_description_index; +}; + +struct mov_elst_t +{ + uint64_t segment_duration; // by Movie Header Box timescale + int64_t media_time; + int16_t media_rate_integer; + int16_t media_rate_fraction; +}; + +struct mov_trex_t +{ +// uint32_t track_ID; + uint32_t default_sample_description_index; + uint32_t default_sample_duration; + uint32_t default_sample_size; + uint32_t default_sample_flags; +}; + +struct mov_tfhd_t +{ + uint32_t flags; +// uint32_t track_ID; + uint64_t base_data_offset; + uint32_t sample_description_index; + uint32_t default_sample_duration; + uint32_t default_sample_size; + uint32_t default_sample_flags; +}; + +#endif /* !_mov_atom_h_ */ diff --git a/MediaServer/libmov/include/mov-box.h b/MediaServer/libmov/include/mov-box.h new file mode 100644 index 0000000..8d38048 --- /dev/null +++ b/MediaServer/libmov/include/mov-box.h @@ -0,0 +1,25 @@ +#ifndef _mov_box_h_ +#define _mov_box_h_ + +#include +#include + +// ISO/IEC 14496-12:2012(E) 4.2 Object Structure (16) +struct mov_box_t +{ + uint64_t size; // 0-size: box extends to end of file, 1-size: large size + uint32_t type; + + // if 'uuid' == type + //uint8_t usertype[16]; + + // FullBox + //uint32_t version : 8; + //uint32_t flags : 24; + +#if defined(DEBUG) || defined(_DEBUG) + int level; +#endif +}; + +#endif /* !_mov_box_h_ */ diff --git a/MediaServer/libmov/include/mov-buffer.h b/MediaServer/libmov/include/mov-buffer.h new file mode 100644 index 0000000..a15f31e --- /dev/null +++ b/MediaServer/libmov/include/mov-buffer.h @@ -0,0 +1,33 @@ +#ifndef _mov_buffer_h_ +#define _mov_buffer_h_ + +#include + +struct mov_buffer_t +{ + /// read data from buffer + /// @param[in] param user-defined parameter + /// @param[out] data user buffer + /// @param[in] bytes data buffer size + /// @return 0-ok, <0-error + int (*read)(void* param, void* data, uint64_t bytes); + + /// write data to buffer + /// @param[in] param user-defined parameter + /// @param[in] data user buffer + /// @param[in] bytes data buffer size + /// @return 0-ok, <0-error + int (*write)(void* param, const void* data, uint64_t bytes); + + /// move buffer position + /// @param[in] param user-defined parameter + /// @param[in] offset >=0-seek buffer read/write position to offset(from buffer begin), <0-seek from file end(SEEK_END) + /// @return 0-ok, <0-error + int (*seek)(void* param, int64_t offset); + + /// get buffer read/write position + /// @return <0-error, other-current read/write position + int64_t (*tell)(void* param); +}; + +#endif /* !_mov_buffer_h_ */ diff --git a/MediaServer/libmov/include/mov-format.h b/MediaServer/libmov/include/mov-format.h new file mode 100644 index 0000000..4106d32 --- /dev/null +++ b/MediaServer/libmov/include/mov-format.h @@ -0,0 +1,54 @@ +#ifndef _mov_format_h_ +#define _mov_format_h_ + +// ISO/IEC 14496-1:2010(E) 7.2.6.6 DecoderConfigDescriptor (p48) +// MPEG-4 systems ObjectTypeIndication +// http://www.mp4ra.org/object.html +#define MOV_OBJECT_TEXT 0x08 // Text Stream +#define MOV_OBJECT_MP4V 0x20 // Visual ISO/IEC 14496-2 (c) +#define MOV_OBJECT_H264 0x21 // Visual ITU-T Recommendation H.264 | ISO/IEC 14496-10 +#define MOV_OBJECT_H265 0x23 // Visual ISO/IEC 23008-2 | ITU-T Recommendation H.265 +#define MOV_OBJECT_AAC 0x40 // Audio ISO/IEC 14496-3 +#define MOV_OBJECT_MP2V 0x60 // Visual ISO/IEC 13818-2 Simple Profile +#define MOV_OBJECT_AAC_MAIN 0x66 // MPEG-2 AAC Main +#define MOV_OBJECT_AAC_LOW 0x67 // MPEG-2 AAC Low +#define MOV_OBJECT_AAC_SSR 0x68 // MPEG-2 AAC SSR +#define MOV_OBJECT_MP3 0x69 // Audio ISO/IEC 13818-3 +#define MOV_OBJECT_MP1V 0x6A // Visual ISO/IEC 11172-2 +#define MOV_OBJECT_MP1A 0x6B // Audio ISO/IEC 11172-3 +#define MOV_OBJECT_JPEG 0x6C // Visual ISO/IEC 10918-1 (JPEG) +#define MOV_OBJECT_PNG 0x6D // Portable Network Graphics (f) +#define MOV_OBJECT_JPEG2000 0x6E // Visual ISO/IEC 15444-1 (JPEG 2000) +#define MOV_OBJECT_VC1 0xA3 // SMPTE VC-1 Video +#define MOV_OBJECT_DIRAC 0xA4 // Dirac Video Coder +#define MOV_OBJECT_AC3 0xA5 // AC-3 +#define MOV_OBJECT_EAC3 0xA6 // Enhanced AC-3 +#define MOV_OBJECT_G719 0xA8 // ITU G.719 Audio +#define MOV_OBJECT_DTS 0xA9 // Core Substream +#define MOV_OBJECT_OPUS 0xAD // Opus audio https://opus-codec.org/docs/opus_in_isobmff.html +#define MOV_OBJECT_VP9 0xB1 // VP9 Video +#define MOV_OBJECT_FLAC 0xC1 // nonstandard from FFMPEG +#define MOV_OBJECT_VP8 0xC2 // nonstandard +#define MOV_OBJECT_CHAPTER 0xC3 // chapter https://developer.apple.com/documentation/quicktime-file-format/base_media_information_header_atom +#define MOV_OBJECT_H266 0xFC // ITU-T Recommendation H.266 +#define MOV_OBJECT_G711a 0xFD // ITU G.711 alaw +#define MOV_OBJECT_G711u 0xFE // ITU G.711 ulaw +#define MOV_OBJECT_AV1 0xFF // AV1: https://aomediacodec.github.io/av1-isobmff + +#define MOV_OBJECT_NONE 0x00 // unknown object id +#define MOV_OBJECT_AVC MOV_OBJECT_H264 +#define MOV_OBJECT_HEVC MOV_OBJECT_H265 +#define MOV_OBJECT_VVC MOV_OBJECT_H266 +#define MOV_OBJECT_ALAW MOV_OBJECT_G711a +#define MOV_OBJECT_ULAW MOV_OBJECT_G711u + +/// MOV flags +#define MOV_FLAG_FASTSTART 0x00000001 +#define MOV_FLAG_SEGMENT 0x00000002 // fmp4_writer only + +/// MOV av stream flag +#define MOV_AV_FLAG_KEYFREAME 0x0001 +#define MOV_AV_FLAG_SEGMENT_FORCE 0x8000 // exclude with MOV_AV_FLAG_SEGMENT_DISABLE, fmp4_writer only +#define MOV_AV_FLAG_SEGMENT_DISABLE 0x4000 // exclude with MOV_AV_FLAG_SEGMENT_FORCE, fmp4_writer only + +#endif /* !_mov_format_h_ */ diff --git a/MediaServer/libmov/include/mov-memory-buffer.h b/MediaServer/libmov/include/mov-memory-buffer.h new file mode 100644 index 0000000..292791e --- /dev/null +++ b/MediaServer/libmov/include/mov-memory-buffer.h @@ -0,0 +1,89 @@ +#ifndef _mov_memory_buffer_h_ +#define _mov_memory_buffer_h_ + +#include "mov-buffer.h" +#include +#include +#include +#include +#include + +struct mov_memory_buffer_t +{ + uint8_t* ptr; + uint64_t bytes; + + uint64_t off; + uint64_t capacity; + uint64_t maxsize; // limit +}; + +static int mov_memory_read(void* param, void* data, uint64_t bytes) +{ + struct mov_memory_buffer_t* ptr; + ptr = (struct mov_memory_buffer_t*)param; + if (ptr->off + bytes > ptr->bytes) + return -E2BIG; + + memcpy(data, ptr->ptr + ptr->off, (size_t)bytes); + ptr->off += bytes; + return 0; +} + +static int mov_memory_write(void* param, const void* data, uint64_t bytes) +{ + void* p; + uint64_t capacity; + struct mov_memory_buffer_t* ptr; + ptr = (struct mov_memory_buffer_t*)param; + if (ptr->off + bytes > ptr->maxsize) + return -E2BIG; + + if (ptr->off + bytes > ptr->capacity) + { + capacity = ptr->off + bytes + 1 * 1024 * 1024; + capacity = capacity > ptr->maxsize ? ptr->maxsize : capacity; + p = realloc(ptr->ptr, capacity); + if (NULL == p) + return -ENOMEM; + ptr->ptr = (uint8_t*)p; + ptr->capacity = capacity; + } + + memcpy(ptr->ptr + ptr->off, data, bytes); + ptr->off += bytes; + + if (ptr->off > ptr->bytes) + ptr->bytes = ptr->off; + return 0; +} + +static int mov_memory_seek(void* param, int64_t offset) +{ + struct mov_memory_buffer_t* ptr; + ptr = (struct mov_memory_buffer_t*)param; + if ((uint64_t)(offset >= 0 ? offset : -offset) > ptr->capacity) + return -E2BIG; + ptr->off = offset >= 0 ? offset : (ptr->capacity+offset); + return 0; +} + +static int64_t mov_memory_tell(void* param) +{ + struct mov_memory_buffer_t* ptr; + ptr = (struct mov_memory_buffer_t*)param; + return (int64_t)ptr->off; +} + +static inline const struct mov_buffer_t* mov_memory_buffer(void) +{ + static struct mov_buffer_t s_io = { + mov_memory_read, + mov_memory_write, + mov_memory_seek, + mov_memory_tell, + }; + return &s_io; +} + +#endif /* !_mov_memory_buffer_h_ */ diff --git a/MediaServer/libmov/include/mov-reader.h b/MediaServer/libmov/include/mov-reader.h new file mode 100644 index 0000000..0fbcb7a --- /dev/null +++ b/MediaServer/libmov/include/mov-reader.h @@ -0,0 +1,54 @@ +#ifndef _mov_reader_h_ +#define _mov_reader_h_ + +#include +#include +#include "mov-buffer.h" + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct mov_reader_t mov_reader_t; + +mov_reader_t* mov_reader_create(const struct mov_buffer_t* buffer, void* param); +void mov_reader_destroy(mov_reader_t* mov); + +struct mov_reader_trackinfo_t +{ + /// @param[in] object: MOV_OBJECT_H264/MOV_OBJECT_AAC, see more @mov-format.h + void (*onvideo)(void* param, uint32_t track, uint8_t object, int width, int height, const void* extra, size_t bytes); + void (*onaudio)(void* param, uint32_t track, uint8_t object, int channel_count, int bit_per_sample, int sample_rate, const void* extra, size_t bytes); + void (*onsubtitle)(void* param, uint32_t track, uint8_t object, const void* extra, size_t bytes); +}; + +/// @return 0-OK, other-error +int mov_reader_getinfo(mov_reader_t* mov, struct mov_reader_trackinfo_t *ontrack, void* param); + +uint64_t mov_reader_getduration(mov_reader_t* mov); + +/// audio: AAC raw data, don't include ADTS/AudioSpecificConfig +/// video: 4-byte data length(don't include self length) + H.264 NALU(don't include 0x00000001) +/// @param[in] flags MOV_AV_FLAG_xxx, such as: MOV_AV_FLAG_KEYFREAME +typedef void (*mov_reader_onread)(void* param, uint32_t track, const void* buffer, size_t bytes, int64_t pts, int64_t dts, int flags); +/// @return 1-read one frame, 0-EOF, <0-error +int mov_reader_read(mov_reader_t* mov, void* buffer, size_t bytes, mov_reader_onread onread, void* param); + +/// audio: AAC raw data, don't include ADTS/AudioSpecificConfig +/// video: 4-byte data length(don't include self length) + H.264 NALU(don't include 0x00000001) +/// @param[in] flags MOV_AV_FLAG_xxx, such as: MOV_AV_FLAG_KEYFREAME +/// @return NULL-error, other-user alloc buffer +typedef void* (*mov_reader_onread2)(void* param, uint32_t track, size_t bytes, int64_t pts, int64_t dts, int flags); +/// same as mov_reader_read + user alloc buffer +/// NOTICE: user should free buffer on return error!!! +/// @return 1-read one frame, 0-EOF, <0-error +int mov_reader_read2(mov_reader_t* mov, mov_reader_onread2 onread, void* param); + +/// @param[in,out] timestamp input seek timestamp, output seek location timestamp +/// @return 0-ok, other-error +int mov_reader_seek(mov_reader_t* mov, int64_t* timestamp); + +#ifdef __cplusplus +} +#endif +#endif /* !_mov_reader_h_*/ diff --git a/MediaServer/libmov/include/mov-udta.h b/MediaServer/libmov/include/mov-udta.h new file mode 100644 index 0000000..0f4238a --- /dev/null +++ b/MediaServer/libmov/include/mov-udta.h @@ -0,0 +1,53 @@ +#ifndef _mov_udta_h_ +#define _mov_udta_h_ + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + + +// https://developer.apple.com/library/archive/documentation/QuickTime/QTFF/Metadata/Metadata.html + +struct mov_udta_meta_t +{ + //char* title; + //char* artist; + //char* album_artist; + //char* album; + //char* date; + //char* comment; + //char* genre; + //char* copyright; + //char* lyrics; + //char* description; + //char* synopsis; + //char* show; + //char* episode_id; + //char* network; + //char* keywords; + //char* season_num; + //char* media_type; + //char* hd_video; + //char* gapless_playback; + //char* compilation; + + uint8_t* cover; // cover binary data, jpeg/png only + int cover_size; // cover binnary data length in byte +}; + +int mov_udta_meta_write(const struct mov_udta_meta_t* meta, void* data, int bytes); + +struct mov_udta_chapter_t +{ + uint64_t timestamp; // 1s = 10 * 1000 * 1000 + const char* title; // optional +}; +int mov_udta_chapter_write(const struct mov_udta_chapter_t* chapters, int count, void* data, int bytes); + +#ifdef __cplusplus +} +#endif +#endif /* !_mov_udta_h_ */ diff --git a/MediaServer/libmov/include/mov-writer.h b/MediaServer/libmov/include/mov-writer.h new file mode 100644 index 0000000..96554fb --- /dev/null +++ b/MediaServer/libmov/include/mov-writer.h @@ -0,0 +1,40 @@ +#ifndef _mov_writer_h_ +#define _mov_writer_h_ + +#include +#include +#include "mov-buffer.h" + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct mov_writer_t mov_writer_t; + +/// @param[in] flags mov flags, such as: MOV_FLAG_FASTSTART, see more @mov-format.h +mov_writer_t* mov_writer_create(const struct mov_buffer_t* buffer, void* param, int flags); +void mov_writer_destroy(mov_writer_t* mov); + +/// @param[in] object MPEG-4 systems ObjectTypeIndication such as: MOV_OBJECT_H264, see more @mov-format.h +/// @param[in] extra_data AudioSpecificConfig/AVCDecoderConfigurationRecord/HEVCDecoderConfigurationRecord +/// @return >=0-track, <0-error +int mov_writer_add_audio(mov_writer_t* mov, uint8_t object, int channel_count, int bits_per_sample, int sample_rate, const void* extra_data, size_t extra_data_size); +int mov_writer_add_video(mov_writer_t* mov, uint8_t object, int width, int height, const void* extra_data, size_t extra_data_size); +int mov_writer_add_subtitle(mov_writer_t* mov, uint8_t object, const void* extra_data, size_t extra_data_size); + +/// Write audio/video stream +/// raw AAC data, don't include ADTS/AudioSpecificConfig +/// H.264/H.265 MP4 format, replace start code(0x00000001) with NALU size +/// @param[in] track return by mov_writer_add_audio/mov_writer_add_video +/// @param[in] data audio/video frame +/// @param[in] bytes buffer size +/// @param[in] pts timestamp in millisecond +/// @param[in] dts timestamp in millisecond +/// @param[in] flags MOV_AV_FLAG_XXX, such as: MOV_AV_FLAG_KEYFREAME, see more @mov-format.h +/// @return 0-ok, other-error +int mov_writer_write(mov_writer_t* mov, int track, const void* data, size_t bytes, int64_t pts, int64_t dts, int flags); + +#ifdef __cplusplus +} +#endif +#endif /* !_mov_writer_h_ */ diff --git a/MediaServer/libmov/include/mp4-writer.h b/MediaServer/libmov/include/mp4-writer.h new file mode 100644 index 0000000..e037423 --- /dev/null +++ b/MediaServer/libmov/include/mp4-writer.h @@ -0,0 +1,131 @@ +#ifndef _mp4_writer_h_ +#define _mp4_writer_h_ + +#include +#include +#include +#include +#include "mov-buffer.h" +#include "mov-format.h" +#include "mov-writer.h" +#include "fmp4-writer.h" + +#ifdef __cplusplus +extern "C" { +#endif + +struct mp4_writer_t +{ + mov_writer_t *mov; + fmp4_writer_t* fmp4; +}; + +/// @param[in] flags mov flags, such as: MOV_FLAG_SEGMENT, see more @mov-format.h +static inline struct mp4_writer_t* mp4_writer_create(int is_fmp4, const struct mov_buffer_t* buffer, void* param, int flags) +{ + struct mp4_writer_t* mp4; + mp4 = (struct mp4_writer_t*)calloc(1, sizeof(struct mp4_writer_t)); + if (!mp4) return NULL; + + if (!is_fmp4) { + mp4->mov = mov_writer_create(buffer, param, flags); + } else { + mp4->fmp4 = fmp4_writer_create(buffer, param, flags); + } + return mp4; +} + +static inline void mp4_writer_destroy(struct mp4_writer_t* mp4) +{ + assert((mp4->fmp4 && !mp4->mov) || (!mp4->fmp4 && mp4->mov)); + if (mp4->mov) { + mov_writer_destroy(mp4->mov); + } else { + fmp4_writer_destroy(mp4->fmp4); + } + free(mp4); +} + +/// @param[in] object MPEG-4 systems ObjectTypeIndication such as: MOV_OBJECT_AAC, see more @mov-format.h +/// @param[in] extra_data AudioSpecificConfig +/// @return >=0-track, <0-error +static inline int mp4_writer_add_audio(struct mp4_writer_t* mp4, uint8_t object, int channel_count, int bits_per_sample, int sample_rate, const void* extra_data, size_t extra_data_size) +{ + assert((mp4->fmp4 && !mp4->mov) || (!mp4->fmp4 && mp4->mov)); + if (mp4->mov) { + return mov_writer_add_audio(mp4->mov, object, channel_count, bits_per_sample, sample_rate, extra_data, extra_data_size); + } else { + return fmp4_writer_add_audio(mp4->fmp4, object, channel_count, bits_per_sample, sample_rate, extra_data, extra_data_size); + } +} + +/// @param[in] object MPEG-4 systems ObjectTypeIndication such as: MOV_OBJECT_H264, see more @mov-format.h +/// @param[in] extra_data AVCDecoderConfigurationRecord/HEVCDecoderConfigurationRecord +/// @return >=0-track, <0-error +static inline int mp4_writer_add_video(struct mp4_writer_t* mp4, uint8_t object, int width, int height, const void* extra_data, size_t extra_data_size) +{ + assert((mp4->fmp4 && !mp4->mov) || (!mp4->fmp4 && mp4->mov)); + if (mp4->mov) { + return mov_writer_add_video(mp4->mov, object, width, height, extra_data, extra_data_size); + } else { + return fmp4_writer_add_video(mp4->fmp4, object, width, height, extra_data, extra_data_size); + } +} + +static inline int mp4_writer_add_subtitle(struct mp4_writer_t* mp4, uint8_t object, const void* extra_data, size_t extra_data_size) +{ + assert((mp4->fmp4 && !mp4->mov) || (!mp4->fmp4 && mp4->mov)); + if (mp4->mov) { + return mov_writer_add_subtitle(mp4->mov, object, extra_data, extra_data_size); + } else { + return fmp4_writer_add_subtitle(mp4->fmp4, object, extra_data, extra_data_size); + } +} + +/// Write audio/video stream +/// raw AAC data, don't include ADTS/AudioSpecificConfig +/// H.264/H.265 MP4 format, replace start code(0x00000001) with NALU size +/// @param[in] track return by mov_writer_add_audio/mov_writer_add_video +/// @param[in] data audio/video frame +/// @param[in] bytes buffer size +/// @param[in] pts timestamp in millisecond +/// @param[in] dts timestamp in millisecond +/// @param[in] flags MOV_AV_FLAG_XXX, such as: MOV_AV_FLAG_KEYFREAME, see more @mov-format.h +/// @return 0-ok, other-error +static inline int mp4_writer_write(struct mp4_writer_t* mp4, int track, const void* data, size_t bytes, int64_t pts, int64_t dts, int flags) +{ + assert((mp4->fmp4 && !mp4->mov) || (!mp4->fmp4 && mp4->mov)); + if (mp4->mov) { + return mov_writer_write(mp4->mov, track, data, bytes, pts, dts, flags); + } else { + return fmp4_writer_write(mp4->fmp4, track, data, bytes, pts, dts, flags); + } +} + +///////////////////// The following interfaces are only applicable to fmp4 /////////////////////////////// + +/// Save data and open next segment +/// @return 0-ok, other-error +static inline int mp4_writer_save_segment(struct mp4_writer_t* mp4) +{ + assert((mp4->fmp4 && !mp4->mov) || (!mp4->fmp4 && mp4->mov)); + if (mp4->fmp4) + return fmp4_writer_save_segment(mp4->fmp4); + return 0; +} + +/// Get init segment data(write FTYP, MOOV only) +/// WARNING: it caller duty to switch file/buffer context with fmp4_writer_write +/// @return 0-ok, other-error +static inline int mp4_writer_init_segment(struct mp4_writer_t* mp4) +{ + assert((mp4->fmp4 && !mp4->mov) || (!mp4->fmp4 && mp4->mov)); + if (mp4->fmp4) + return fmp4_writer_init_segment(mp4->fmp4); + return 0; +} + +#ifdef __cplusplus +} +#endif +#endif /* !_mp4_writer_h_ */ diff --git a/MediaServer/libmov/source/fmp4-reader.c b/MediaServer/libmov/source/fmp4-reader.c new file mode 100644 index 0000000..f11b6cb --- /dev/null +++ b/MediaServer/libmov/source/fmp4-reader.c @@ -0,0 +1,119 @@ +#include "mov-internal.h" +#include +#include + +#define DIFF(a, b) ((a) > (b) ? ((a) - (b)) : ((b) - (a))) + +static int mov_fragment_seek_get_duration(struct mov_t* mov) +{ + int i; + struct mov_track_t* track; + track = mov->track_count > 0 ? &mov->tracks[0] : NULL; + if (track && track->frag_capacity < track->frag_count && track->mdhd.timescale) + { + mov_buffer_seek(&mov->io, track->frags[track->frag_count - 1].offset); + mov_reader_root(mov); // moof + + track->mdhd.duration = track->samples[track->sample_count - 1].dts - track->samples[0].dts; + mov->mvhd.duration = track->mdhd.duration * mov->mvhd.timescale / track->mdhd.timescale; + + // clear samples and seek to the first moof + for (i = 0; i < mov->track_count; i++) + { + mov->tracks[i].sample_count = 0; + mov->tracks[i].sample_offset = 0; + } + track->frag_capacity = 0; + } + + return 0; +} + +int mov_fragment_seek_read_mfra(struct mov_t* mov) +{ + uint64_t pos; + pos = mov_buffer_tell(&mov->io); // for fallback + mov_buffer_seek(&mov->io, -16); + mov_reader_root(mov); // mfro + if (mov->mfro > 0) + { + mov_buffer_seek(&mov->io, -((int64_t)mov->mfro)); + mov_reader_root(mov); // mfra + mov_fragment_seek_get_duration(mov); // for get fmp4 duration + } + mov_buffer_seek(&mov->io, pos); + return mov_buffer_error(&mov->io); +} + +int mov_fragment_seek(struct mov_t* mov, int64_t* timestamp) +{ + int i; + uint64_t clock; + size_t idx, start, end; + struct mov_track_t* track; + struct mov_fragment_t* frag, *prev, *next; + + track = mov->track_count > 0 ? &mov->tracks[0] : NULL; + if (!track || track->frag_count < 1) + return -1; + + idx = start = 0; + end = track->frag_count; + assert(track->frag_count > 0); + clock = (uint64_t)(*timestamp) * track->mdhd.timescale / 1000; // mvhd timescale + + while (start < end) + { + idx = (start + end) / 2; + frag = &track->frags[idx]; + + if (frag->time > clock) + end = idx; + else if (frag->time < clock) + start = idx + 1; + else + break; + } + + frag = &track->frags[idx]; + prev = &track->frags[idx > 0 ? idx - 1 : idx]; + next = &track->frags[idx + 1 < track->frag_count ? idx + 1 : idx]; + if (DIFF(prev->time, clock) < DIFF(frag->time, clock)) + frag = prev; + if (DIFF(next->time, clock) < DIFF(frag->time, clock)) + frag = next; + + *timestamp = frag->time * 1000 / track->mdhd.timescale; + + // clear samples and seek + for (i = 0; i < mov->track_count; i++) + { + mov->tracks[i].sample_count = 0; + mov->tracks[i].sample_offset = 0; + } + track->frag_capacity = (uint32_t)idx; + return 0; +} + +int mov_fragment_read_next_moof(struct mov_t* mov) +{ + int i; + struct mov_track_t* track; + + // clear moof samples + for (i = 0; i < mov->track_count; i++) + { + mov->tracks[i].sample_count = 0; + mov->tracks[i].sample_offset = 0; + } + + track = mov->track_count > 0 ? &mov->tracks[0] : NULL; + if (track && track->frag_capacity < track->frag_count) + { + mov_buffer_seek(&mov->io, track->frags[track->frag_capacity++].offset); + mov_reader_root(mov); // moof + return 0; + } + + return 1; // eof +} diff --git a/MediaServer/libmov/source/fmp4-writer.c b/MediaServer/libmov/source/fmp4-writer.c new file mode 100644 index 0000000..b8a10b5 --- /dev/null +++ b/MediaServer/libmov/source/fmp4-writer.c @@ -0,0 +1,545 @@ +#include "fmp4-writer.h" +#include "mov-internal.h" +#include +#include +#include +#include +#include + +struct fmp4_writer_t +{ + struct mov_t mov; + size_t mdat_size; + int has_moov; + + uint32_t frag_interleave; + uint32_t fragment_id; // start from 1 + uint32_t sn; // sample sn +}; + +static int fmp4_write_app(struct mov_t* mov) +{ + mov_buffer_w32(&mov->io, 8 + strlen(MOV_APP)); /* size */ + mov_buffer_write(&mov->io, "free", 4); + mov_buffer_write(&mov->io, MOV_APP, strlen(MOV_APP)); + return 0; +} + +static size_t fmp4_write_mvex(struct mov_t* mov) +{ + int i; + size_t size; + uint64_t offset; + + size = 8 /* Box */; + offset = mov_buffer_tell(&mov->io); + mov_buffer_w32(&mov->io, 0); /* size */ + mov_buffer_write(&mov->io, "mvex", 4); + + size += mov_write_mehd(mov); + for (i = 0; i < mov->track_count; i++) + { + mov->track = mov->tracks + i; + size += mov_write_trex(mov); + } + //size += mov_write_leva(mov); + + mov_write_size(mov, offset, size); /* update size */ + return size; +} + +static size_t fmp4_write_traf(struct mov_t* mov, uint32_t moof) +{ + uint32_t i, start; + size_t size; + uint64_t offset; + struct mov_track_t* track; + + size = 8 /* Box */; + offset = mov_buffer_tell(&mov->io); + mov_buffer_w32(&mov->io, 0); /* size */ + mov_buffer_write(&mov->io, "traf", 4); + + track = mov->track; + track->tfhd.flags = MOV_TFHD_FLAG_DEFAULT_FLAGS /*| MOV_TFHD_FLAG_BASE_DATA_OFFSET*/; + track->tfhd.flags |= MOV_TFHD_FLAG_SAMPLE_DESCRIPTION_INDEX; + // ISO/IEC 23009-1:2014(E) 6.3.4.2 General format type (p93) + // The 'moof' boxes shall use movie-fragment relative addressing for media data that + // does not use external data references, the flag 'default-base-is-moof' shall be set, + // and data-offset shall be used, i.e. base-data-offset-present shall not be used. + //if (mov->flags & MOV_FLAG_SEGMENT) + { + //track->tfhd.flags &= ~MOV_TFHD_FLAG_BASE_DATA_OFFSET; + track->tfhd.flags |= MOV_TFHD_FLAG_DEFAULT_BASE_IS_MOOF; + } + track->tfhd.base_data_offset = mov->moof_offset; + track->tfhd.sample_description_index = 1; + track->tfhd.default_sample_flags = MOV_AUDIO == track->handler_type ? MOV_TREX_FLAG_SAMPLE_DEPENDS_ON_I_PICTURE : (MOV_TREX_FLAG_SAMPLE_IS_NO_SYNC_SAMPLE| MOV_TREX_FLAG_SAMPLE_DEPENDS_ON_NOT_I_PICTURE); + if (track->sample_count > 0) + { + track->tfhd.flags |= MOV_TFHD_FLAG_DEFAULT_DURATION | MOV_TFHD_FLAG_DEFAULT_SIZE; + track->tfhd.default_sample_duration = track->sample_count > 1 ? (uint32_t)(track->samples[1].dts - track->samples[0].dts) : (uint32_t)track->turn_last_duration; + track->tfhd.default_sample_size = track->samples[0].bytes; + } + else + { + track->tfhd.flags |= MOV_TFHD_FLAG_DURATION_IS_EMPTY; + track->tfhd.default_sample_duration = 0; // not set + track->tfhd.default_sample_size = 0; // not set + } + + size += mov_write_tfhd(mov); + // ISO/IEC 23009-1:2014(E) 6.3.4.2 General format type (p93) + // Each 'traf' box shall contain a 'tfdt' box. + size += mov_write_tfdt(mov); + + for (start = 0, i = 1; i < track->sample_count; i++) + { + if (track->samples[i - 1].offset + track->samples[i - 1].bytes != track->samples[i].offset) + { + size += mov_write_trun(mov, start, i-start, moof); + start = i; + } + } + size += mov_write_trun(mov, start, i-start, moof); + + mov_write_size(mov, offset, size); /* update size */ + return size; +} + +static size_t fmp4_write_moof(struct mov_t* mov, uint32_t fragment, uint32_t moof) +{ + int i; + size_t size, j; + uint64_t offset; + uint64_t n; + + size = 8 /* Box */; + offset = mov_buffer_tell(&mov->io); + mov_buffer_w32(&mov->io, 0); /* size */ + mov_buffer_write(&mov->io, "moof", 4); + + size += mov_write_mfhd(mov, fragment); + + n = 0; + for (i = 0; i < mov->track_count; i++) + { + mov->track = mov->tracks + i; + + // rewrite offset, write only one trun + // 2017/10/17 Dale Curtis SHA-1: a5fd8aa45b11c10613e6e576033a6b5a16b9cbb9 (libavformat/mov.c) + for (j = 0; j < mov->track->sample_count; j++) + { + mov->track->samples[j].offset = n; + n += mov->track->samples[j].bytes; + } + + if (mov->track->sample_count > 0) + size += fmp4_write_traf(mov, moof); + } + + mov_write_size(mov, offset, size); /* update size */ + return size; +} + +static size_t fmp4_write_moov(struct mov_t* mov) +{ + int i; + size_t size; + uint32_t count; + uint64_t offset; + + size = 8 /* Box */; + offset = mov_buffer_tell(&mov->io); + mov_buffer_w32(&mov->io, 0); /* size */ + mov_buffer_write(&mov->io, "moov", 4); + + size += mov_write_mvhd(mov); +// size += fmp4_write_iods(mov); + for (i = 0; i < mov->track_count; i++) + { + mov->track = mov->tracks + i; + count = mov->track->sample_count; + mov->track->sample_count = 0; + size += mov_write_trak(mov); + mov->track->sample_count = count; // restore sample count + } + + size += fmp4_write_mvex(mov); + size += mov_write_udta(mov); + mov_write_size(mov, offset, size); /* update size */ + return size; +} + +static size_t fmp4_write_sidx(struct mov_t* mov) +{ + int i; + for (i = 0; i < mov->track_count; i++) + { + mov->track = mov->tracks + i; + mov_write_sidx(mov, 52 * (uint64_t)(mov->track_count - i - 1)); /* first_offset */ + } + + return 52 * mov->track_count; +} + +static int fmp4_write_mfra(struct mov_t* mov) +{ + int i; + uint64_t mfra_offset; + uint64_t mfro_offset; + + // mfra + mfra_offset = mov_buffer_tell(&mov->io); + mov_buffer_w32(&mov->io, 0); /* size */ + mov_buffer_write(&mov->io, "mfra", 4); + + // tfra + for (i = 0; i < mov->track_count; i++) + { + mov->track = mov->tracks + i; + mov_write_tfra(mov); + } + + // mfro + mfro_offset = mov_buffer_tell(&mov->io); + mov_buffer_w32(&mov->io, 16); /* size */ + mov_buffer_write(&mov->io, "mfro", 4); + mov_buffer_w32(&mov->io, 0); /* version & flags */ + mov_buffer_w32(&mov->io, (uint32_t)(mfro_offset - mfra_offset + 16)); + + mov_write_size(mov, mfra_offset, (size_t)(mfro_offset - mfra_offset + 16)); + return (int)(mfro_offset - mfra_offset + 16); +} + +static int fmp4_add_fragment_entry(struct mov_track_t* track, uint64_t time, uint64_t offset) +{ + if (track->frag_count >= track->frag_capacity) + { + void* p = realloc(track->frags, sizeof(struct mov_fragment_t) * (track->frag_capacity + 64)); + if (!p) return -ENOMEM; + track->frags = p; + track->frag_capacity += 64; + } + + track->frags[track->frag_count].time = time; + track->frags[track->frag_count].offset = offset; + ++track->frag_count; + return 0; +} + +static int fmp4_write_fragment(struct fmp4_writer_t* writer) +{ + int i; + size_t n; + size_t refsize; + struct mov_t* mov; + mov = &writer->mov; + + if (writer->mdat_size < 1) + return 0; // empty + + // write moov + if (mov->flags & MOV_FLAG_SEGMENT) + { + // write stype + mov_write_styp(mov); + + // ISO/IEC 23009-1:2014(E) 6.3.4.2 General format type (p93) + // Each Media Segment may contain one or more 'sidx' boxes. + // If present, the first 'sidx' box shall be placed before any 'moof' box + // and the first Segment Index box shall document the entire Segment. + fmp4_write_sidx(mov); + } + else if (!writer->has_moov) + { + mov_write_ftyp(mov); + fmp4_write_app(mov); + fmp4_write_moov(mov); + writer->has_moov = 1; + } + + // moof + mov->moof_offset = mov_buffer_tell(&mov->io); + refsize = fmp4_write_moof(mov, ++writer->fragment_id, 0); // start from 1 + // rewrite moof with trun data offset + mov_buffer_seek(&mov->io, mov->moof_offset); + fmp4_write_moof(mov, writer->fragment_id, (uint32_t)refsize+8); + refsize += writer->mdat_size + 8/*mdat box*/; + + // add mfra entry + for (i = 0; i < mov->track_count; i++) + { + mov->track = mov->tracks + i; + if (mov->track->sample_count > 0 && 0 == (mov->flags & MOV_FLAG_SEGMENT)) + fmp4_add_fragment_entry(mov->track, mov->track->samples[0].dts, mov->moof_offset); + + // hack: write sidx referenced_size + if (mov->flags & MOV_FLAG_SEGMENT) + mov_write_size(mov, mov->moof_offset - 52 * (uint64_t)(mov->track_count - i) + 40, (0 << 31) | (refsize & 0x7fffffff)); + + mov->track->offset = 0; // reset + } + + // mdat + if (writer->mdat_size + 8 <= UINT32_MAX) + { + mov_buffer_w32(&mov->io, (uint32_t)writer->mdat_size + 8); /* size */ + mov_buffer_write(&mov->io, "mdat", 4); + } + else + { + mov_buffer_w32(&mov->io, 1); + mov_buffer_write(&mov->io, "mdat", 4); + mov_buffer_w64(&mov->io, writer->mdat_size + 16); + } + + // interleave write samples + n = 0; + while(n < writer->mdat_size) + { + for (i = 0; i < mov->track_count; i++) + { + mov->track = mov->tracks + i; + while (mov->track->offset < mov->track->sample_count && n == mov->track->samples[mov->track->offset].offset) + { + mov_buffer_write(&mov->io, mov->track->samples[mov->track->offset].data, mov->track->samples[mov->track->offset].bytes); + free(mov->track->samples[mov->track->offset].data); // free av packet memory + n += mov->track->samples[mov->track->offset].bytes; + ++mov->track->offset; + } + } + } + + // clear track samples(don't free samples memory) + for (i = 0; i < mov->track_count; i++) + { + mov->tracks[i].sample_count = 0; + mov->tracks[i].offset = 0; + } + writer->mdat_size = 0; + + return mov_buffer_error(&mov->io); +} + +static int fmp4_writer_init(struct mov_t* mov) +{ + if (mov->flags & MOV_FLAG_SEGMENT) + { + mov->ftyp.major_brand = MOV_BRAND_MSDH; + mov->ftyp.minor_version = 0; + mov->ftyp.brands_count = 6; + mov->ftyp.compatible_brands[0] = MOV_BRAND_ISOM; + mov->ftyp.compatible_brands[1] = MOV_BRAND_MP42; + mov->ftyp.compatible_brands[2] = MOV_BRAND_MSDH; + mov->ftyp.compatible_brands[3] = MOV_BRAND_MSIX; + mov->ftyp.compatible_brands[4] = MOV_BRAND_ISO5; // default�\base�\is�\moof flag + mov->ftyp.compatible_brands[5] = MOV_BRAND_ISO6; // styp + mov->header = 0; + } + else + { + mov->ftyp.major_brand = MOV_BRAND_ISOM; + mov->ftyp.minor_version = 1; + mov->ftyp.brands_count = 5; + mov->ftyp.compatible_brands[0] = MOV_BRAND_ISOM; + mov->ftyp.compatible_brands[1] = MOV_BRAND_MP42; + mov->ftyp.compatible_brands[2] = MOV_BRAND_AVC1; + mov->ftyp.compatible_brands[3] = MOV_BRAND_DASH; + mov->ftyp.compatible_brands[4] = MOV_BRAND_ISO5; // default�\base�\is�\moof flag + mov->header = 0; + } + return 0; +} + +struct fmp4_writer_t* fmp4_writer_create(const struct mov_buffer_t *buffer, void* param, int flags) +{ + struct mov_t* mov; + struct fmp4_writer_t* writer; + writer = (struct fmp4_writer_t*)calloc(1, sizeof(struct fmp4_writer_t)); + if (NULL == writer) + return NULL; + + writer->frag_interleave = 5; + + mov = &writer->mov; + mov->flags = flags; + mov->mvhd.next_track_ID = 1; + mov->mvhd.creation_time = time(NULL) + 0x7C25B080; // 1970 based -> 1904 based; + mov->mvhd.modification_time = mov->mvhd.creation_time; + mov->mvhd.timescale = 1000; + mov->mvhd.duration = 0; // placeholder + fmp4_writer_init(mov); + + mov->io.param = param; + memcpy(&mov->io.io, buffer, sizeof(mov->io.io)); + return writer; +} + +void fmp4_writer_destroy(struct fmp4_writer_t* writer) +{ + int i; + struct mov_t* mov; + mov = &writer->mov; + + fmp4_writer_save_segment(writer); + + // write mfra + if (0 == (mov->flags & MOV_FLAG_SEGMENT)) + { + fmp4_write_mfra(mov); + for (i = 0; i < mov->track_count; i++) + mov->tracks[i].frag_count = 0; // don't free frags memory + } + + // mov_buffer_error(&mov->io); + + for (i = 0; i < mov->track_count; i++) + mov_free_track(mov->tracks + i); + if (mov->tracks) + free(mov->tracks); + free(writer); +} + +int fmp4_writer_write(struct fmp4_writer_t* writer, int idx, const void* data, size_t bytes, int64_t pts, int64_t dts, int flags) +{ + int64_t duration; + struct mov_track_t* track; + struct mov_sample_t* sample; + + if (idx < 0 || idx >= (int)writer->mov.track_count) + return -ENOENT; + + track = &writer->mov.tracks[idx]; + + duration = dts > track->last_dts && INT64_MIN != track->last_dts ? dts - track->last_dts : 0; +#if 1 + track->turn_last_duration = duration; +#else + track->turn_last_duration = track->turn_last_duration > 0 ? track->turn_last_duration * 7 / 8 + duration / 8 : duration; +#endif + + // 1. force segment or + // 2. video key frame + if (0 == (flags & MOV_AV_FLAG_SEGMENT_DISABLE) && (0 != (flags & MOV_AV_FLAG_SEGMENT_FORCE) || (MOV_VIDEO == track->handler_type && (flags & MOV_AV_FLAG_KEYFREAME))) ) + fmp4_write_fragment(writer); // fragment per video keyframe + + if (track->sample_count + 1 >= track->sample_offset) + { + void* ptr = realloc(track->samples, sizeof(struct mov_sample_t) * (track->sample_offset + 1024)); + if (NULL == ptr) return -ENOMEM; + track->samples = (struct mov_sample_t*)ptr; + track->sample_offset += 1024; + } + + pts = pts * track->mdhd.timescale / 1000; + dts = dts * track->mdhd.timescale / 1000; + + sample = &track->samples[track->sample_count]; + sample->sample_description_index = 1; + sample->bytes = (uint32_t)bytes; + sample->flags = flags; + sample->pts = pts; + sample->dts = dts; + sample->offset = writer->mdat_size; + + sample->data = malloc(bytes); + if (NULL == sample->data) + return -ENOMEM; + memcpy(sample->data, data, bytes); + + if (INT64_MIN == track->start_dts) + track->start_dts = sample->dts; + writer->mdat_size += bytes; // update media data size + track->sample_count += 1; + track->last_dts = sample->dts; + return mov_buffer_error(&writer->mov.io); +} + +int fmp4_writer_add_audio(struct fmp4_writer_t* writer, uint8_t object, int channel_count, int bits_per_sample, int sample_rate, const void* extra_data, size_t extra_data_size) +{ + struct mov_t* mov; + struct mov_track_t* track; + + mov = &writer->mov; + track = mov_add_track(mov); + if (NULL == track) + return -ENOMEM; + + if (0 != mov_add_audio(track, &mov->mvhd, 1000, object, channel_count, bits_per_sample, sample_rate, extra_data, extra_data_size)) + return -ENOMEM; + + mov->mvhd.next_track_ID++; + return mov->track_count++; +} + +int fmp4_writer_add_video(struct fmp4_writer_t* writer, uint8_t object, int width, int height, const void* extra_data, size_t extra_data_size) +{ + struct mov_t* mov; + struct mov_track_t* track; + + mov = &writer->mov; + track = mov_add_track(mov); + if (NULL == track) + return -ENOMEM; + + if (0 != mov_add_video(track, &mov->mvhd, 1000, object, width, height, extra_data, extra_data_size)) + return -ENOMEM; + + mov->mvhd.next_track_ID++; + return mov->track_count++; +} + +int fmp4_writer_add_subtitle(struct fmp4_writer_t* writer, uint8_t object, const void* extra_data, size_t extra_data_size) +{ + struct mov_t* mov; + struct mov_track_t* track; + + mov = &writer->mov; + track = mov_add_track(mov); + if (NULL == track) + return -ENOMEM; + + if (0 != mov_add_subtitle(track, &mov->mvhd, 1000, object, extra_data, extra_data_size)) + return -ENOMEM; + + mov->mvhd.next_track_ID++; + return mov->track_count++; +} + +int fmp4_writer_add_udta(fmp4_writer_t* writer, const void* data, size_t size) +{ + writer->mov.udta = data; + writer->mov.udta_size = size; + return 0; +} + +int fmp4_writer_save_segment(fmp4_writer_t* writer) +{ + //int i; + //struct mov_t* mov; + //mov = &writer->mov; + + // flush fragment + return fmp4_write_fragment(writer); + + //// write mfra + //if (0 == (mov->flags & MOV_FLAG_SEGMENT)) + //{ + // fmp4_write_mfra(mov); + // for (i = 0; i < mov->track_count; i++) + // mov->tracks[i].frag_count = 0; // don't free frags memory + //} + + //return mov_buffer_error(&mov->io); +} + +int fmp4_writer_init_segment(fmp4_writer_t* writer) +{ + struct mov_t* mov; + mov = &writer->mov; + mov_write_ftyp(mov); + fmp4_write_moov(mov); + writer->has_moov = 1; + return mov_buffer_error(&mov->io); +} diff --git a/MediaServer/libmov/source/mov-avc1.c b/MediaServer/libmov/source/mov-avc1.c new file mode 100644 index 0000000..915981a --- /dev/null +++ b/MediaServer/libmov/source/mov-avc1.c @@ -0,0 +1,76 @@ +#include "mov-internal.h" +#include +#include +#include + +int mov_read_extra(struct mov_t* mov, const struct mov_box_t* box) +{ + struct mov_track_t* track = mov->track; + struct mov_sample_entry_t* entry = track->stsd.current; + if (entry->extra_data_size < box->size) + { + void* p = realloc(entry->extra_data, (size_t)box->size); + if (NULL == p) return -ENOMEM; + entry->extra_data = p; + } + + mov_buffer_read(&mov->io, entry->extra_data, box->size); + entry->extra_data_size = (int)box->size; + return mov_buffer_error(&mov->io); +} + +// extra_data: ISO/IEC 14496-15 AVCDecoderConfigurationRecord +size_t mov_write_avcc(const struct mov_t* mov) +{ + const struct mov_track_t* track = mov->track; + const struct mov_sample_entry_t* entry = track->stsd.current; + mov_buffer_w32(&mov->io, entry->extra_data_size + 8); /* size */ + mov_buffer_write(&mov->io, "avcC", 4); + if (entry->extra_data_size > 0) + mov_buffer_write(&mov->io, entry->extra_data, entry->extra_data_size); + return entry->extra_data_size + 8; +} + +// extra_data: ISO/IEC 14496-15:2017 HEVCDecoderConfigurationRecord +size_t mov_write_hvcc(const struct mov_t* mov) +{ + const struct mov_track_t* track = mov->track; + const struct mov_sample_entry_t* entry = track->stsd.current; + mov_buffer_w32(&mov->io, entry->extra_data_size + 8); /* size */ + mov_buffer_write(&mov->io, "hvcC", 4); + if (entry->extra_data_size > 0) + mov_buffer_write(&mov->io, entry->extra_data, entry->extra_data_size); + return entry->extra_data_size + 8; +} + +// https://aomediacodec.github.io/av1-isobmff +// extra data: AV1CodecConfigurationRecord + +size_t mov_write_av1c(const struct mov_t* mov) +{ + const struct mov_track_t* track = mov->track; + const struct mov_sample_entry_t* entry = track->stsd.current; + mov_buffer_w32(&mov->io, entry->extra_data_size + 8); /* size */ + mov_buffer_write(&mov->io, "av1C", 4); + if (entry->extra_data_size > 0) + mov_buffer_write(&mov->io, entry->extra_data, entry->extra_data_size); + return entry->extra_data_size + 8; +} + + +// extra_data: ISO/IEC 14496-15 AVCDecoderConfigurationRecord +/* +class VvcConfigurationBox extends FullBox('vvcC',version=0,flags) { + VvcDecoderConfigurationRecord() VvcConfig; +} +*/ +size_t mov_write_vvcc(const struct mov_t* mov) +{ + const struct mov_track_t* track = mov->track; + const struct mov_sample_entry_t* entry = track->stsd.current; + mov_buffer_w32(&mov->io, entry->extra_data_size + 8); /* size */ + mov_buffer_write(&mov->io, "vvcC", 4); + if (entry->extra_data_size > 0) + mov_buffer_write(&mov->io, entry->extra_data, entry->extra_data_size); + return entry->extra_data_size + 8; +} diff --git a/MediaServer/libmov/source/mov-dinf.c b/MediaServer/libmov/source/mov-dinf.c new file mode 100644 index 0000000..6f462d8 --- /dev/null +++ b/MediaServer/libmov/source/mov-dinf.c @@ -0,0 +1,32 @@ +#include "mov-internal.h" + +size_t mov_write_dref(const struct mov_t* mov) +{ + mov_buffer_w32(&mov->io, 28); /* size */ + mov_buffer_write(&mov->io, "dref", 4); + mov_buffer_w32(&mov->io, 0); /* version & flags */ + mov_buffer_w32(&mov->io, 1); /* entry count */ + + mov_buffer_w32(&mov->io, 12); /* size */ + //FIXME add the alis and rsrc atom + mov_buffer_write(&mov->io, "url ", 4); + mov_buffer_w32(&mov->io, 1); /* version & flags */ + + return 28; +} + +size_t mov_write_dinf(const struct mov_t* mov) +{ + size_t size; + uint64_t offset; + + size = 8 /* Box */; + offset = mov_buffer_tell(&mov->io); + mov_buffer_w32(&mov->io, 0); /* size */ + mov_buffer_write(&mov->io, "dinf", 4); + + size += mov_write_dref(mov); + + mov_write_size(mov, offset, size); /* update size */ + return size; +} diff --git a/MediaServer/libmov/source/mov-elst.c b/MediaServer/libmov/source/mov-elst.c new file mode 100644 index 0000000..e7497ce --- /dev/null +++ b/MediaServer/libmov/source/mov-elst.c @@ -0,0 +1,136 @@ +#include "mov-internal.h" +#include +#include +#include + +// 8.6.6 Edit List Box (p53) +int mov_read_elst(struct mov_t* mov, const struct mov_box_t* box) +{ + uint32_t i, entry_count; + uint32_t version; + struct mov_track_t* track = mov->track; + + version = mov_buffer_r8(&mov->io); /* version */ + mov_buffer_r24(&mov->io); /* flags */ + entry_count = mov_buffer_r32(&mov->io); + + assert(0 == track->elst_count && NULL == track->elst); + if (track->elst_count < entry_count) + { + void* p = realloc(track->elst, sizeof(struct mov_elst_t) * entry_count); + if (NULL == p) return -ENOMEM; + track->elst = (struct mov_elst_t*)p; + } + track->elst_count = entry_count; + + for (i = 0; i < entry_count; i++) + { + if (1 == version) + { + track->elst[i].segment_duration = mov_buffer_r64(&mov->io); + track->elst[i].media_time = (int64_t)mov_buffer_r64(&mov->io); + } + else + { + assert(0 == version); + track->elst[i].segment_duration = mov_buffer_r32(&mov->io); + track->elst[i].media_time = (int32_t)mov_buffer_r32(&mov->io); + } + track->elst[i].media_rate_integer = (int16_t)mov_buffer_r16(&mov->io); + track->elst[i].media_rate_fraction = (int16_t)mov_buffer_r16(&mov->io); + } + + (void)box; + return mov_buffer_error(&mov->io); +} + +size_t mov_write_elst(const struct mov_t* mov) +{ + uint32_t size; + int64_t time; + int64_t delay; + uint8_t version; + const struct mov_track_t* track = mov->track; + + assert(track->start_dts == track->samples[0].dts); + version = track->tkhd.duration > UINT32_MAX ? 1 : 0; + + // in media time scale units, in composition time + time = track->samples[0].pts - track->samples[0].dts; + // in units of the timescale in the Movie Header Box + delay = track->samples[0].pts * mov->mvhd.timescale / track->mdhd.timescale; + if (delay > UINT32_MAX) + version = 1; + + time = time < 0 ? 0 : time; + size = 12/* full box */ + 4/* entry count */ + (delay > 0 ? 2 : 1) * (version ? 20 : 12); + + mov_buffer_w32(&mov->io, size); /* size */ + mov_buffer_write(&mov->io, "elst", 4); + mov_buffer_w8(&mov->io, version); /* version */ + mov_buffer_w24(&mov->io, 0); /* flags */ + mov_buffer_w32(&mov->io, delay > 0 ? 2 : 1); /* entry count */ + + if (delay > 0) + { + if (1 == version) + { + mov_buffer_w64(&mov->io, (uint64_t)delay); /* segment_duration */ + mov_buffer_w64(&mov->io, (uint64_t)-1); /* media_time */ + } + else + { + mov_buffer_w32(&mov->io, (uint32_t)delay); + mov_buffer_w32(&mov->io, (uint32_t)-1); + } + + mov_buffer_w16(&mov->io, 1); /* media_rate_integer */ + mov_buffer_w16(&mov->io, 0); /* media_rate_fraction */ + } + + /* duration */ + if (version == 1) + { + mov_buffer_w64(&mov->io, track->tkhd.duration); + mov_buffer_w64(&mov->io, time); + } + else + { + mov_buffer_w32(&mov->io, (uint32_t)track->tkhd.duration); + mov_buffer_w32(&mov->io, (uint32_t)time); + } + mov_buffer_w16(&mov->io, 1); /* media_rate_integer */ + mov_buffer_w16(&mov->io, 0); /* media_rate_fraction */ + + return size; +} + +void mov_apply_elst(struct mov_track_t *track) +{ + size_t i; + + // edit list + track->samples[0].dts = 0; + track->samples[0].pts = 0; + for (i = 0; i < track->elst_count; i++) + { + if (-1 == track->elst[i].media_time) + { + track->samples[0].dts = track->elst[i].segment_duration; + track->samples[0].pts = track->samples[0].dts; + } + } +} + +void mov_apply_elst_tfdt(struct mov_track_t *track) +{ + size_t i; + + for (i = 0; i < track->elst_count; i++) + { + if (-1 == track->elst[i].media_time) + { + track->tfdt_dts += track->elst[i].segment_duration; + } + } +} diff --git a/MediaServer/libmov/source/mov-esds.c b/MediaServer/libmov/source/mov-esds.c new file mode 100644 index 0000000..654dca6 --- /dev/null +++ b/MediaServer/libmov/source/mov-esds.c @@ -0,0 +1,391 @@ +#include "mov-internal.h" +#include +#include +#include + +static int mp4_read_tag(struct mov_t* mov, uint64_t bytes); + +// ISO/IEC 14496-1:2010(E) +// 7.2.2 Common data structures +// Table-1 List of Class Tags for Descriptors (p31) +enum { + ISO_ObjectDescrTag = 0x01, + ISO_InitialObjectDescrTag = 0x02, + ISO_ESDescrTag = 0x03, + ISO_DecoderConfigDescrTag = 0x04, + ISO_DecSpecificInfoTag = 0x05, + ISO_SLConfigDescrTag = 0x06, + ISO_ContentIdentDescrTag = 0x07, + ISO_SupplContentIdentDescrTag = 0x08, + ISO_IPI_DescrPointerTag = 0x09, + ISO_IPMP_DescrPointerTag = 0x0A, + ISO_IPMP_DescrTag = 0x0B, + ISO_QoS_DescrTag = 0x0C, + ISO_RegistrationDescrTag = 0x0D, + ISO_ES_ID_IncTag = 0x0E, + ISO_ES_ID_RefTag = 0x0F, + ISO_MP4_IOD_Tag = 0x10, + ISO_MP4_OD_Tag = 0x11, +}; + +// ISO/IEC 14496-1:2010(E) +// 7.2.2.3 BaseCommand +// Table-2 List of Class Tags for Commands (p33) +enum { + ISO_ObjectDescrUpdateTag = 0x01, + ISO_ObjectDescrRemoveTag = 0x02, + ISO_ES_DescrUpdateTag = 0x03, + ISO_ES_DescrRemoveTag = 0x04, + ISO_IPMP_DescrUpdateTag = 0x05, + ISO_IPMP_DescrRemoveTag = 0x06, + ISO_ES_DescrRemoveRefTag = 0x07, + ISO_ObjectDescrExecuteTag = 0x08, + ISO_User_Private = 0xC0, +}; + +// ISO/IEC 14496-1:2010(E) 7.2.2.2 BaseDescriptor (p32) +// ISO/IEC 14496-1:2010(E) 8.3.3 Expandable classes (p116) +/* +abstract aligned(8) expandable(2^28-1) class BaseDescriptor : bit(8) tag=0 { + // empty. To be filled by classes extending this class. +} + +int sizeOfInstance = 0; +bit(1) nextByte; +bit(7) sizeOfInstance; +while(nextByte) { + bit(1) nextByte; + bit(7) sizeByte; + sizeOfInstance = sizeOfInstance<<7 | sizeByte; +} +*/ +static int mov_read_base_descr(struct mov_t* mov, int bytes, int* tag, int* len) +{ + int i; + uint32_t c; + + *tag = mov_buffer_r8(&mov->io); + *len = 0; + c = 0x80; + for (i = 0; i < 4 && i + 1 < bytes && 0 != (c & 0x80); i++) + { + c = mov_buffer_r8(&mov->io); + *len = (*len << 7) | (c & 0x7F); + //if (0 == (c & 0x80)) + // break; + } + return 1 + i; +} + +static uint32_t mov_write_base_descr(const struct mov_t* mov, uint8_t tag, uint32_t len) +{ + mov_buffer_w8(&mov->io, tag); + mov_buffer_w8(&mov->io, (uint8_t)(0x80 | (len >> 21))); + mov_buffer_w8(&mov->io, (uint8_t)(0x80 | (len >> 14))); + mov_buffer_w8(&mov->io, (uint8_t)(0x80 | (len >> 7))); + mov_buffer_w8(&mov->io, (uint8_t)(0x7F & len)); + return 5; +} + +// ISO/IEC 14496-1:2010(E) 7.2.6.5 ES_Descriptor (p47) +/* +class ES_Descriptor extends BaseDescriptor : bit(8) tag=ES_DescrTag { + bit(16) ES_ID; + bit(1) streamDependenceFlag; + bit(1) URL_Flag; + bit(1) OCRstreamFlag; + bit(5) streamPriority; + if (streamDependenceFlag) + bit(16) dependsOn_ES_ID; + if (URL_Flag) { + bit(8) URLlength; + bit(8) URLstring[URLlength]; + } + if (OCRstreamFlag) + bit(16) OCR_ES_Id; + DecoderConfigDescriptor decConfigDescr; + if (ODProfileLevelIndication==0x01) //no SL extension. + { + SLConfigDescriptor slConfigDescr; + } + else // SL extension is possible. + { + SLConfigDescriptor slConfigDescr; + } + IPI_DescrPointer ipiPtr[0 .. 1]; + IP_IdentificationDataSet ipIDS[0 .. 255]; + IPMP_DescriptorPointer ipmpDescrPtr[0 .. 255]; + LanguageDescriptor langDescr[0 .. 255]; + QoS_Descriptor qosDescr[0 .. 1]; + RegistrationDescriptor regDescr[0 .. 1]; + ExtensionDescriptor extDescr[0 .. 255]; +} +*/ +static int mp4_read_es_descriptor(struct mov_t* mov, uint64_t bytes) +{ + uint64_t p1, p2; + p1 = mov_buffer_tell(&mov->io); + /*uint32_t ES_ID = */mov_buffer_r16(&mov->io); + uint32_t flags = mov_buffer_r8(&mov->io); + if (flags & 0x80) //streamDependenceFlag + mov_buffer_r16(&mov->io); + if (flags & 0x40) { //URL_Flag + uint32_t n = mov_buffer_r8(&mov->io); + mov_buffer_skip(&mov->io, n); + } + + if (flags & 0x20) //OCRstreamFlag + mov_buffer_r16(&mov->io); + + p2 = mov_buffer_tell(&mov->io); + return mp4_read_tag(mov, bytes - (p2 - p1)); +} + +// ISO/IEC 14496-1:2010(E) 7.2.6.7 DecoderSpecificInfo (p51) +/* +abstract class DecoderSpecificInfo extends BaseDescriptor : bit(8) + tag=DecSpecificInfoTag +{ + // empty. To be filled by classes extending this class. +} +*/ +static int mp4_read_decoder_specific_info(struct mov_t* mov, int len) +{ + struct mov_track_t* track = mov->track; + struct mov_sample_entry_t* entry = track->stsd.current; + if (entry->extra_data_size < len) + { + void* p = realloc(entry->extra_data, len); + if (NULL == p) return -ENOMEM; + entry->extra_data = p; + } + + mov_buffer_read(&mov->io, entry->extra_data, len); + entry->extra_data_size = len; + return mov_buffer_error(&mov->io); +} + +static int mp4_write_decoder_specific_info(const struct mov_t* mov) +{ + const struct mov_sample_entry_t* entry = mov->track->stsd.current; + mov_write_base_descr(mov, ISO_DecSpecificInfoTag, entry->extra_data_size); + mov_buffer_write(&mov->io, entry->extra_data, entry->extra_data_size); + return entry->extra_data_size; +} + +// ISO/IEC 14496-1:2010(E) 7.2.6.6 DecoderConfigDescriptor (p48) +/* +class DecoderConfigDescriptor extends BaseDescriptor : bit(8) tag=DecoderConfigDescrTag { + bit(8) objectTypeIndication; + bit(6) streamType; + bit(1) upStream; + const bit(1) reserved=1; + bit(24) bufferSizeDB; + bit(32) maxBitrate; + bit(32) avgBitrate; + DecoderSpecificInfo decSpecificInfo[0 .. 1]; + profileLevelIndicationIndexDescriptor profileLevelIndicationIndexDescr[0..255]; +} +*/ +static int mp4_read_decoder_config_descriptor(struct mov_t* mov, int len) +{ + struct mov_sample_entry_t* entry = mov->track->stsd.current; + entry->object_type_indication = (uint8_t)mov_buffer_r8(&mov->io); /* objectTypeIndication */ + entry->stream_type = (uint8_t)mov_buffer_r8(&mov->io) >> 2; /* stream type */ + /*uint32_t bufferSizeDB = */mov_buffer_r24(&mov->io); /* buffer size db */ + /*uint32_t max_rate = */mov_buffer_r32(&mov->io); /* max bit-rate */ + /*uint32_t bit_rate = */mov_buffer_r32(&mov->io); /* avg bit-rate */ + return mp4_read_tag(mov, (uint64_t)len - 13); // mp4_read_decoder_specific_info +} + +static int mp4_write_decoder_config_descriptor(const struct mov_t* mov) +{ + const struct mov_sample_entry_t* entry = mov->track->stsd.current; + int size = 13 + (entry->extra_data_size > 0 ? entry->extra_data_size + 5 : 0); + mov_write_base_descr(mov, ISO_DecoderConfigDescrTag, size); + mov_buffer_w8(&mov->io, entry->object_type_indication); + mov_buffer_w8(&mov->io, 0x01/*reserved*/ | (entry->stream_type << 2)); + mov_buffer_w24(&mov->io, 0); /* buffer size db */ + mov_buffer_w32(&mov->io, 88360); /* max bit-rate */ + mov_buffer_w32(&mov->io, 88360); /* avg bit-rate */ + + if (entry->extra_data_size > 0) + mp4_write_decoder_specific_info(mov); + + return size; +} + +// ISO/IEC 14496-1:2010(E) 7.3.2.3 SL Packet Header Configuration (p92) +/* +class SLConfigDescriptor extends BaseDescriptor : bit(8) tag=SLConfigDescrTag { + bit(8) predefined; + if (predefined==0) { + bit(1) useAccessUnitStartFlag; + bit(1) useAccessUnitEndFlag; + bit(1) useRandomAccessPointFlag; + bit(1) hasRandomAccessUnitsOnlyFlag; + bit(1) usePaddingFlag; + bit(1) useTimeStampsFlag; + bit(1) useIdleFlag; + bit(1) durationFlag; + bit(32) timeStampResolution; + bit(32) OCRResolution; + bit(8) timeStampLength; // must be ¡Ü 64 + bit(8) OCRLength; // must be ¡Ü 64 + bit(8) AU_Length; // must be ¡Ü 32 + bit(8) instantBitrateLength; + bit(4) degradationPriorityLength; + bit(5) AU_seqNumLength; // must be ¡Ü 16 + bit(5) packetSeqNumLength; // must be ¡Ü 16 + bit(2) reserved=0b11; + } + if (durationFlag) { + bit(32) timeScale; + bit(16) accessUnitDuration; + bit(16) compositionUnitDuration; + } + if (!useTimeStampsFlag) { + bit(timeStampLength) startDecodingTimeStamp; + bit(timeStampLength) startCompositionTimeStamp; + } +} + +class ExtendedSLConfigDescriptor extends SLConfigDescriptor : bit(8) +tag=ExtSLConfigDescrTag { + SLExtensionDescriptor slextDescr[1..255]; +} +*/ +static int mp4_read_sl_config_descriptor(struct mov_t* mov) +{ + int flags = 0; + int predefined = mov_buffer_r8(&mov->io); + if (0 == predefined) + { + flags = mov_buffer_r8(&mov->io); + /*uint32_t timeStampResolution = */mov_buffer_r32(&mov->io); + /*uint32_t OCRResolution = */mov_buffer_r32(&mov->io); + /*int timeStampLength = */mov_buffer_r8(&mov->io); + /*int OCRLength = */mov_buffer_r8(&mov->io); + /*int AU_Length = */mov_buffer_r8(&mov->io); + /*int instantBitrateLength = */mov_buffer_r8(&mov->io); + /*uint16_t length = */mov_buffer_r16(&mov->io); + } + else if (1 == predefined) // null SL packet header + { + flags = 0x00; + //int TimeStampResolution = 1000; + //int timeStampLength = 32; + } + else if (2 == predefined) // Reserved for use in MP4 files + { + // Table 14 ¡ª Detailed predefined SLConfigDescriptor values (p93) + flags = 0x04; + } + + // durationFlag + if (flags & 0x01) + { + /*uint32_t timeScale = */mov_buffer_r32(&mov->io); + /*uint16_t accessUnitDuration = */mov_buffer_r16(&mov->io); + /*uint16_t compositionUnitDuration = */mov_buffer_r16(&mov->io); + } + + // useTimeStampsFlag + if (0 == (flags & 0x04)) + { + //uint64_t startDecodingTimeStamp = 0; // file_reader_rb8(timeStampLength / 8) + //uint64_t startCompositionTimeStamp = 0; // file_reader_rb8(timeStampLength / 8) + } + return mov_buffer_error(&mov->io); +} + +static size_t mp4_write_sl_config_descriptor(const struct mov_t* mov) +{ + size_t size = 1; + size += mov_write_base_descr(mov, ISO_SLConfigDescrTag, 1); + mov_buffer_w8(&mov->io, 0x02); + return size; +} + +static int mp4_read_tag(struct mov_t* mov, uint64_t bytes) +{ + int tag, len; + uint64_t p1, p2, offset; + + for (offset = 0; offset < bytes; offset += len) + { + tag = len = 0; + offset += mov_read_base_descr(mov, (int)(bytes - offset), &tag, &len); + if (offset + len > bytes) + break; + + p1 = mov_buffer_tell(&mov->io); + switch (tag) + { + case ISO_ESDescrTag: + mp4_read_es_descriptor(mov, len); + break; + + case ISO_DecoderConfigDescrTag: + mp4_read_decoder_config_descriptor(mov, len); + break; + + case ISO_DecSpecificInfoTag: + mp4_read_decoder_specific_info(mov, len); + break; + + case ISO_SLConfigDescrTag: + mp4_read_sl_config_descriptor(mov); + break; + + default: + break; + } + + p2 = mov_buffer_tell(&mov->io); + mov_buffer_skip(&mov->io, len - (p2 - p1)); + } + + return mov_buffer_error(&mov->io); +} + +// ISO/IEC 14496-14:2003(E) 5.6 Sample Description Boxes (p15) +int mov_read_esds(struct mov_t* mov, const struct mov_box_t* box) +{ + mov_buffer_r8(&mov->io); /* version */ + mov_buffer_r24(&mov->io); /* flags */ + return mp4_read_tag(mov, box->size - 4); +} + +static size_t mp4_write_es_descriptor(const struct mov_t* mov) +{ + uint32_t size = 3; // mp4_write_decoder_config_descriptor + const struct mov_sample_entry_t* entry = mov->track->stsd.current; + size += 5 + 13 + (entry->extra_data_size > 0 ? entry->extra_data_size + 5 : 0); // mp4_write_decoder_config_descriptor + size += 5 + 1; // mp4_write_sl_config_descriptor + + size += mov_write_base_descr(mov, ISO_ESDescrTag, size); + mov_buffer_w16(&mov->io, (uint16_t)mov->track->tkhd.track_ID); // ES_ID + mov_buffer_w8(&mov->io, 0x00); // flags (= no flags) + + mp4_write_decoder_config_descriptor(mov); + mp4_write_sl_config_descriptor(mov); + return size; +} + +size_t mov_write_esds(const struct mov_t* mov) +{ + size_t size; + uint64_t offset; + + size = 12 /* full box */; + offset = mov_buffer_tell(&mov->io); + mov_buffer_w32(&mov->io, 0); /* size */ + mov_buffer_write(&mov->io, "esds", 4); + mov_buffer_w32(&mov->io, 0); /* version & flags */ + + size += mp4_write_es_descriptor(mov); + + mov_write_size(mov, offset, size); /* update size */ + return size; +} diff --git a/MediaServer/libmov/source/mov-ftyp.c b/MediaServer/libmov/source/mov-ftyp.c new file mode 100644 index 0000000..69af7c1 --- /dev/null +++ b/MediaServer/libmov/source/mov-ftyp.c @@ -0,0 +1,54 @@ +#include "mov-internal.h" +#include + +// 4.3 File Type Box (p17) +int mov_read_ftyp(struct mov_t* mov, const struct mov_box_t* box) +{ + if(box->size < 8) return -1; + + mov->ftyp.major_brand = mov_buffer_r32(&mov->io); + mov->ftyp.minor_version = mov_buffer_r32(&mov->io); + + for(mov->ftyp.brands_count = 0; mov->ftyp.brands_count < N_BRAND && (uint64_t)mov->ftyp.brands_count * 4 < box->size - 8; ++mov->ftyp.brands_count) + { + mov->ftyp.compatible_brands[mov->ftyp.brands_count] = mov_buffer_r32(&mov->io); + } + + assert(box->size == 4 * (uint64_t)mov->ftyp.brands_count + 8); + mov_buffer_skip(&mov->io, box->size - 4 * (uint64_t)mov->ftyp.brands_count - 8 ); // skip compatible_brands + return 0; +} + +size_t mov_write_ftyp(const struct mov_t* mov) +{ + int size, i; + + size = 8/* box */ + 8/* item */ + mov->ftyp.brands_count * 4 /* compatible brands */; + + mov_buffer_w32(&mov->io, size); /* size */ + mov_buffer_write(&mov->io, "ftyp", 4); + mov_buffer_w32(&mov->io, mov->ftyp.major_brand); + mov_buffer_w32(&mov->io, mov->ftyp.minor_version); + + for (i = 0; i < mov->ftyp.brands_count; i++) + mov_buffer_w32(&mov->io, mov->ftyp.compatible_brands[i]); + + return size; +} + +size_t mov_write_styp(const struct mov_t* mov) +{ + int size, i; + + size = 8/* box */ + 8/* item */ + mov->ftyp.brands_count * 4 /* compatible brands */; + + mov_buffer_w32(&mov->io, size); /* size */ + mov_buffer_write(&mov->io, "styp", 4); + mov_buffer_w32(&mov->io, mov->ftyp.major_brand); + mov_buffer_w32(&mov->io, mov->ftyp.minor_version); + + for (i = 0; i < mov->ftyp.brands_count; i++) + mov_buffer_w32(&mov->io, mov->ftyp.compatible_brands[i]); + + return size; +} diff --git a/MediaServer/libmov/source/mov-hdlr.c b/MediaServer/libmov/source/mov-hdlr.c new file mode 100644 index 0000000..c27c578 --- /dev/null +++ b/MediaServer/libmov/source/mov-hdlr.c @@ -0,0 +1,43 @@ +#include "mov-internal.h" +#include +#include + +// 8.4.3 Handler Reference Box (p36) +// Box Type: 'hdlr' +// Container: Media Box ('mdia') or Meta Box ('meta') +// Mandatory: Yes +// Quantity: Exactly one +int mov_read_hdlr(struct mov_t* mov, const struct mov_box_t* box) +{ + struct mov_track_t* track = mov->track; + + mov_buffer_r8(&mov->io); /* version */ + mov_buffer_r24(&mov->io); /* flags */ + //uint32_t pre_defined = mov_buffer_r32(&mov->io); + mov_buffer_skip(&mov->io, 4); + track->handler_type = mov_buffer_r32(&mov->io); + // const unsigned int(32)[3] reserved = 0; + mov_buffer_skip(&mov->io, 12); + // string name; + mov_buffer_skip(&mov->io, box->size - 24); // String name + return 0; +} + +size_t mov_write_hdlr(const struct mov_t* mov) +{ + const struct mov_track_t* track = mov->track; + + mov_buffer_w32(&mov->io, 33 + (uint32_t)strlen(track->handler_descr)); /* size */ + mov_buffer_write(&mov->io, "hdlr", 4); + mov_buffer_w32(&mov->io, 0); /* Version & flags */ + + mov_buffer_w32(&mov->io, 0); /* pre_defined */ + mov_buffer_w32(&mov->io, track->handler_type); /* handler_type */ + + mov_buffer_w32(&mov->io, 0); /* reserved */ + mov_buffer_w32(&mov->io, 0); /* reserved */ + mov_buffer_w32(&mov->io, 0); /* reserved */ + + mov_buffer_write(&mov->io, track->handler_descr, (uint64_t)strlen(track->handler_descr)+1); /* name */ + return 33 + strlen(track->handler_descr); +} diff --git a/MediaServer/libmov/source/mov-hdr.c b/MediaServer/libmov/source/mov-hdr.c new file mode 100644 index 0000000..3c9ba3b --- /dev/null +++ b/MediaServer/libmov/source/mov-hdr.c @@ -0,0 +1,37 @@ +#include "mov-internal.h" +#include +#include +#include + +// https://www.webmproject.org/vp9/mp4/ + +int mov_read_smdm(struct mov_t* mov, const struct mov_box_t* box) +{ + (void)box; + mov_buffer_r8(&mov->io); // version + mov_buffer_r24(&mov->io); // flags + + mov_buffer_r16(&mov->io); // primaryRChromaticity_x, 0.16 fixed-point Red X chromaticity coordinate as defined by CIE 1931 + mov_buffer_r16(&mov->io); // primaryRChromaticity_y + mov_buffer_r16(&mov->io); // primaryGChromaticity_x + mov_buffer_r16(&mov->io); // primaryGChromaticity_y + mov_buffer_r16(&mov->io); // primaryBChromaticity_x + mov_buffer_r16(&mov->io); // primaryBChromaticity_y + mov_buffer_r16(&mov->io); // whitePointChromaticity_x + mov_buffer_r16(&mov->io); // whitePointChromaticity_y + mov_buffer_r32(&mov->io); // luminanceMax, 24.8 fixed point Maximum luminance, represented in candelas per square meter (cd/m²) + mov_buffer_r32(&mov->io); // luminanceMin + + return mov_buffer_error(&mov->io); +} + +int mov_read_coll(struct mov_t* mov, const struct mov_box_t* box) +{ + (void)box; + mov_buffer_r8(&mov->io); // version + mov_buffer_r24(&mov->io); // flags + + mov_buffer_r16(&mov->io); // maxCLL, Maximum Content Light Level as specified in CEA-861.3, Appendix A. + mov_buffer_r16(&mov->io); // maxFALL, Maximum Frame-Average Light Level as specified in CEA-861.3, Appendix A. + return mov_buffer_error(&mov->io); +} diff --git a/MediaServer/libmov/source/mov-internal.h b/MediaServer/libmov/source/mov-internal.h new file mode 100644 index 0000000..38f9c7f --- /dev/null +++ b/MediaServer/libmov/source/mov-internal.h @@ -0,0 +1,335 @@ +#ifndef _mov_internal_h_ +#define _mov_internal_h_ + +#include "mov-box.h" +#include "mov-atom.h" +#include "mov-format.h" +#include "mov-buffer.h" +#include "mov-ioutil.h" + +#define MOV_APP "ireader/media-server" + +#define MOV_TAG(a, b, c, d) (((a) << 24) | ((b) << 16) | ((c) << 8) | (d)) + +#define MOV_MOOV MOV_TAG('m', 'o', 'o', 'v') +#define MOV_ROOT MOV_TAG('r', 'o', 'o', 't') +#define MOV_TRAK MOV_TAG('t', 'r', 'a', 'k') +#define MOV_MDIA MOV_TAG('m', 'd', 'i', 'a') +#define MOV_EDTS MOV_TAG('e', 'd', 't', 's') +#define MOV_MINF MOV_TAG('m', 'i', 'n', 'f') +#define MOV_GMHD MOV_TAG('g', 'm', 'h', 'd') // Apple QuickTime gmhd(text media) +#define MOV_DINF MOV_TAG('d', 'i', 'n', 'f') +#define MOV_STBL MOV_TAG('s', 't', 'b', 'l') +#define MOV_MVEX MOV_TAG('m', 'v', 'e', 'x') +#define MOV_MOOF MOV_TAG('m', 'o', 'o', 'f') +#define MOV_TRAF MOV_TAG('t', 'r', 'a', 'f') +#define MOV_MFRA MOV_TAG('m', 'f', 'r', 'a') + +#define MOV_VIDEO MOV_TAG('v', 'i', 'd', 'e') // ISO/IEC 14496-12:2015(E) 12.1 Video media (p169) +#define MOV_AUDIO MOV_TAG('s', 'o', 'u', 'n') // ISO/IEC 14496-12:2015(E) 12.2 Audio media (p173) +#define MOV_META MOV_TAG('m', 'e', 't', 'a') // ISO/IEC 14496-12:2015(E) 12.3 Metadata media (p181) +#define MOV_HINT MOV_TAG('h', 'i', 'n', 't') // ISO/IEC 14496-12:2015(E) 12.4 Hint media (p183) +#define MOV_TEXT MOV_TAG('t', 'e', 'x', 't') // ISO/IEC 14496-12:2015(E) 12.5 Text media (p184) +#define MOV_SUBT MOV_TAG('s', 'u', 'b', 't') // ISO/IEC 14496-12:2015(E) 12.6 Subtitle media (p185) +#define MOV_FONT MOV_TAG('f', 'd', 's', 'm') // ISO/IEC 14496-12:2015(E) 12.7 Font media (p186) +#define MOV_CLCP MOV_TAG('c', 'l', 'c', 'p') // ClosedCaptionHandler +#define MOV_ALIS MOV_TAG('a', 'l', 'i', 's') // Apple QuickTime Macintosh alias +#define MOV_SBTL MOV_TAG('s', 'b', 't', 'l') // text/tx3g + +// https://developer.apple.com/library/content/documentation/General/Reference/HLSAuthoringSpec/Requirements.html#//apple_ref/doc/uid/TP40016596-CH2-SW1 +// Video encoding requirements 1.10: Use 'avc1', 'hvc1', or 'dvh1' rather than 'avc3', 'hev1', or 'dvhe' +#define MOV_H264 MOV_TAG('a', 'v', 'c', '1') // H.264 ISO/IEC 14496-15:2010(E) 5.3.4 AVC Video Stream Definition (18) +#define MOV_H265 MOV_TAG('h', 'v', 'c', '1') // H.265 +#define MOV_H266 MOV_TAG('v', 'v', 'c', '1') // H.266 +#define MOV_MP4V MOV_TAG('m', 'p', '4', 'v') // MPEG-4 Video +#define MOV_MP4A MOV_TAG('m', 'p', '4', 'a') // AAC +#define MOV_MP4S MOV_TAG('m', 'p', '4', 's') // ISO/IEC 14496-14:2003(E) 5.6 Sample Description Boxes (p14) +#define MOV_OPUS MOV_TAG('O', 'p', 'u', 's') // http://www.opus-codec.org/docs/opus_in_isobmff.html +#define MOV_VP8 MOV_TAG('v', 'p', '0', '8') +#define MOV_VP9 MOV_TAG('v', 'p', '0', '9') // https://www.webmproject.org/vp9/mp4/ +#define MOV_VP10 MOV_TAG('v', 'p', '1', '0') +#define MOV_AV1 MOV_TAG('a', 'v', '0', '1') // https://aomediacodec.github.io/av1-isobmff +#define MOV_VC1 MOV_TAG('v', 'c', '-', '1') +#define MOV_DIRAC MOV_TAG('d', 'r', 'a', 'c') +#define MOV_AC3 MOV_TAG('a', 'c', '-', '3') +#define MOV_DTS MOV_TAG('d', 't', 's', 'c') // DTS-HD + +// ISO/IEC 14496-1:2010(E) 7.2.6.6 DecoderConfigDescriptor +// Table 6 - streamType Values (p51) +enum +{ + MP4_STREAM_ODS = 0x01, /* ObjectDescriptorStream */ + MP4_STREAM_CRS = 0x02, /* ClockReferenceStream */ + MP4_STREAM_SDS = 0x03, /* SceneDescriptionStream */ + MP4_STREAM_VISUAL = 0x04, /* VisualStream */ + MP4_STREAM_AUDIO = 0x05, /* AudioStream */ + MP4_STREAM_MP7 = 0x06, /* MPEG7Stream */ + MP4_STREAM_IPMP = 0x07, /* IPMPStream */ + MP4_STREAM_OCIS = 0x08, /* ObjectContentInfoStream */ + MP4_STREAM_MPEGJ = 0x09, /* MPEGJStream */ + MP4_STREAM_IS = 0x0A, /* Interaction Stream */ + MP4_STREAM_IPMPTOOL = 0x0B, /* IPMPToolStream */ +}; + +enum +{ + MOV_BRAND_ISOM = MOV_TAG('i', 's', 'o', 'm'), + MOV_BRAND_AVC1 = MOV_TAG('a', 'v', 'c', '1'), + MOV_BRAND_ISO2 = MOV_TAG('i', 's', 'o', '2'), + MOV_BRAND_MP71 = MOV_TAG('m', 'p', '7', '1'), + MOV_BRAND_ISO3 = MOV_TAG('i', 's', 'o', '3'), + MOV_BRAND_ISO4 = MOV_TAG('i', 's', 'o', '4'), + MOV_BRAND_ISO5 = MOV_TAG('i', 's', 'o', '5'), + MOV_BRAND_ISO6 = MOV_TAG('i', 's', 'o', '6'), + MOV_BRAND_MP41 = MOV_TAG('m', 'p', '4', '1'), // ISO/IEC 14496-1:2001 MP4 File Format v1 + MOV_BRAND_MP42 = MOV_TAG('m', 'p', '4', '2'), // ISO/IEC 14496-14:2003 MP4 File Format v2 + MOV_BRAND_MOV = MOV_TAG('q', 't', ' ', ' '), // Apple Quick-Time File Format + MOV_BRAND_DASH = MOV_TAG('d', 'a', 's', 'h'), // MPEG-DASH + MOV_BRAND_MSDH = MOV_TAG('m', 's', 'd', 'h'), // MPEG-DASH + MOV_BRAND_MSIX = MOV_TAG('m', 's', 'i', 'x'), // MPEG-DASH +}; + +#define MOV_TREX_FLAG_IS_LEADING_MASK 0x0C000000 +#define MOV_TREX_FLAG_SAMPLE_DEPENDS_ON_MASK 0x03000000 +#define MOV_TREX_FLAG_SAMPLE_IS_DEPENDED_ON_MASK 0x00C00000 +#define MOV_TREX_FLAG_SAMPLE_HAS_REDUNDANCY_MASK 0x00300000 +#define MOV_TREX_FLAG_SAMPLE_PADDING_VALUE_MASK 0x000E0000 +#define MOV_TREX_FLAG_SAMPLE_IS_NO_SYNC_SAMPLE 0x00010000 +#define MOV_TREX_FLAG_SAMPLE_DEGRADATION_PRIORITY_MASK 0x0000FFFF + +// 8.6.4 Independent and Disposable Samples Box (p55) +#define MOV_TREX_FLAG_SAMPLE_DEPENDS_ON_I_PICTURE 0x02000000 +#define MOV_TREX_FLAG_SAMPLE_DEPENDS_ON_NOT_I_PICTURE 0x01000000 + +#define MOV_TFHD_FLAG_BASE_DATA_OFFSET 0x00000001 +#define MOV_TFHD_FLAG_SAMPLE_DESCRIPTION_INDEX 0x00000002 +#define MOV_TFHD_FLAG_DEFAULT_DURATION 0x00000008 +#define MOV_TFHD_FLAG_DEFAULT_SIZE 0x00000010 +#define MOV_TFHD_FLAG_DEFAULT_FLAGS 0x00000020 +#define MOV_TFHD_FLAG_DURATION_IS_EMPTY 0x00010000 +#define MOV_TFHD_FLAG_DEFAULT_BASE_IS_MOOF 0x00020000 + +#define MOV_TRUN_FLAG_DATA_OFFSET_PRESENT 0x0001 +#define MOV_TRUN_FLAG_FIRST_SAMPLE_FLAGS_PRESENT 0x0004 +#define MOV_TRUN_FLAG_SAMPLE_DURATION_PRESENT 0x0100 +#define MOV_TRUN_FLAG_SAMPLE_SIZE_PRESENT 0x0200 +#define MOV_TRUN_FLAG_SAMPLE_FLAGS_PRESENT 0x0400 +#define MOV_TRUN_FLAG_SAMPLE_COMPOSITION_TIME_OFFSET_PRESENT 0x0800 + +#define MOV_TRACK_FLAG_CTTS_V1 0x0001 //ctts version 1 + +struct mov_stbl_t +{ + struct mov_stsc_t* stsc; + size_t stsc_count; + + uint64_t* stco; + uint32_t stco_count; + + struct mov_stts_t* stts; + size_t stts_count; + + struct mov_stts_t* ctts; + size_t ctts_count; + + uint32_t* stss; // sample_number, start from 1 + size_t stss_count; +}; + +struct mov_sample_t +{ + int flags; // MOV_AV_FLAG_KEYFREAME + int64_t pts; // track mdhd timescale + int64_t dts; + + void* data; + uint64_t offset; // is a 32 or 64 bit integer that gives the offset of the start of a chunk into its containing media file. + uint32_t bytes; + + uint32_t sample_description_index; + uint32_t samples_per_chunk; // write only + uint32_t first_chunk; // write only +}; + +struct mov_fragment_t +{ + uint64_t time; + uint64_t offset; // moof offset +}; + +struct mov_track_t +{ + uint32_t tag; // MOV_H264/MOV_MP4A + uint32_t handler_type; // MOV_VIDEO/MOV_AUDIO + const char* handler_descr; // VideoHandler/SoundHandler/SubtitleHandler + + struct mov_tkhd_t tkhd; + struct mov_mdhd_t mdhd; + struct mov_stbl_t stbl; + + // 8.8 Movie Fragments + struct mov_trex_t trex; + struct mov_tfhd_t tfhd; + struct mov_fragment_t* frags; + uint32_t frag_count, frag_capacity /*offset for read*/; + + struct mov_stsd_t stsd; + + struct mov_elst_t* elst; + size_t elst_count; + + uint32_t chpl_track; // MOV_CHAPTER track + + struct mov_sample_t* samples; + uint32_t sample_count; + size_t sample_offset; // sample_capacity + + int64_t tfdt_dts; // tfdt baseMediaDecodeTime + int64_t start_dts; // write fmp4 only + uint64_t offset; // write only + int64_t last_dts; // write fmp4 only + int64_t turn_last_duration; // write fmp4 only + + unsigned int flags; +}; + +struct mov_t +{ + struct mov_ioutil_t io; + + struct mov_ftyp_t ftyp; + struct mov_mvhd_t mvhd; + + int flags; + int header; + uint32_t mfro; // mfro size + uint64_t moof_offset; // last moof offset(from file begin) + uint64_t implicit_offset; + + struct mov_track_t* track; // current stream + struct mov_track_t* tracks; + int track_count; + + const void* udta; + uint64_t udta_size; +}; + +int mov_reader_root(struct mov_t* mov); +int mov_reader_box(struct mov_t* mov, const struct mov_box_t* parent); +int mp4_read_extra(struct mov_t* mov, const struct mov_box_t* parent); + +int mov_read_ftyp(struct mov_t* mov, const struct mov_box_t* box); +int mov_read_mvhd(struct mov_t* mov, const struct mov_box_t* box); +int mov_read_tkhd(struct mov_t* mov, const struct mov_box_t* box); +int mov_read_hdlr(struct mov_t* mov, const struct mov_box_t* box); +int mov_read_mdhd(struct mov_t* mov, const struct mov_box_t* box); +int mov_read_vmhd(struct mov_t* mov, const struct mov_box_t* box); +int mov_read_smhd(struct mov_t* mov, const struct mov_box_t* box); +int mov_read_nmhd(struct mov_t* mov, const struct mov_box_t* box); +int mov_read_esds(struct mov_t* mov, const struct mov_box_t* box); +int mov_read_elst(struct mov_t* mov, const struct mov_box_t* box); +int mov_read_stsd(struct mov_t* mov, const struct mov_box_t* box); +int mov_read_stsz(struct mov_t* mov, const struct mov_box_t* box); +int mov_read_stz2(struct mov_t* mov, const struct mov_box_t* box); +int mov_read_stsc(struct mov_t* mov, const struct mov_box_t* box); +int mov_read_stco(struct mov_t* mov, const struct mov_box_t* box); +int mov_read_stts(struct mov_t* mov, const struct mov_box_t* box); +int mov_read_ctts(struct mov_t* mov, const struct mov_box_t* box); +int mov_read_cslg(struct mov_t* mov, const struct mov_box_t* box); +int mov_read_stss(struct mov_t* mov, const struct mov_box_t* box); +int mov_read_extra(struct mov_t* mov, const struct mov_box_t* box); +int mov_read_vpcc(struct mov_t* mov, const struct mov_box_t* box); +int mov_read_tx3g(struct mov_t* mov, const struct mov_box_t* box); +int mov_read_trex(struct mov_t* mov, const struct mov_box_t* box); +int mov_read_leva(struct mov_t* mov, const struct mov_box_t* box); +int mov_read_tfhd(struct mov_t* mov, const struct mov_box_t* box); +int mov_read_trun(struct mov_t* mov, const struct mov_box_t* box); +int mov_read_tfra(struct mov_t* mov, const struct mov_box_t* box); +int mov_read_sidx(struct mov_t* mov, const struct mov_box_t* box); +int mov_read_mfhd(struct mov_t* mov, const struct mov_box_t* box); +int mov_read_tfdt(struct mov_t* mov, const struct mov_box_t* box); +int mov_read_mehd(struct mov_t* mov, const struct mov_box_t* box); +int mov_read_dops(struct mov_t* mov, const struct mov_box_t* box); +int mov_read_pasp(struct mov_t* mov, const struct mov_box_t* box); +int mov_read_gmin(struct mov_t* mov, const struct mov_box_t* box); +int mov_read_text(struct mov_t* mov, const struct mov_box_t* box); +int mov_read_smdm(struct mov_t* mov, const struct mov_box_t* box); +int mov_read_coll(struct mov_t* mov, const struct mov_box_t* box); +int mov_read_udta(struct mov_t* mov, const struct mov_box_t* box); +int mov_read_chpl(struct mov_t* mov, const struct mov_box_t* box); + +size_t mov_write_ftyp(const struct mov_t* mov); +size_t mov_write_mvhd(const struct mov_t* mov); +size_t mov_write_mdhd(const struct mov_t* mov); +size_t mov_write_tkhd(const struct mov_t* mov); +size_t mov_write_hdlr(const struct mov_t* mov); +size_t mov_write_vmhd(const struct mov_t* mov); +size_t mov_write_smhd(const struct mov_t* mov); +size_t mov_write_nmhd(const struct mov_t* mov); +size_t mov_write_gmhd(const struct mov_t* mov); +size_t mov_write_sthd(const struct mov_t* mov); +size_t mov_write_dinf(const struct mov_t* mov); +size_t mov_write_dref(const struct mov_t* mov); +size_t mov_write_elst(const struct mov_t* mov); +size_t mov_write_stsd(const struct mov_t* mov); +size_t mov_write_stts(const struct mov_t* mov, uint32_t count); +size_t mov_write_ctts(const struct mov_t* mov, uint32_t count); +size_t mov_write_stco(const struct mov_t* mov, uint32_t count); +size_t mov_write_stss(const struct mov_t* mov); +size_t mov_write_stsc(const struct mov_t* mov); +size_t mov_write_stsz(const struct mov_t* mov); +size_t mov_write_esds(const struct mov_t* mov); +size_t mov_write_avcc(const struct mov_t* mov); +size_t mov_write_hvcc(const struct mov_t* mov); +size_t mov_write_vvcc(const struct mov_t* mov); +size_t mov_write_av1c(const struct mov_t* mov); +size_t mov_write_vpcc(const struct mov_t* mov); +size_t mov_write_tx3g(const struct mov_t* mov); +size_t mov_write_trex(const struct mov_t* mov); +size_t mov_write_tfhd(const struct mov_t* mov); +size_t mov_write_trun(const struct mov_t* mov, uint32_t from, uint32_t count, uint32_t offset); +size_t mov_write_tfra(const struct mov_t* mov); +size_t mov_write_styp(const struct mov_t* mov); +size_t mov_write_tfdt(const struct mov_t* mov); +size_t mov_write_mehd(const struct mov_t* mov); +size_t mov_write_sidx(const struct mov_t* mov, uint64_t offset); +size_t mov_write_mfhd(const struct mov_t* mov, uint32_t fragment); +size_t mov_write_edts(const struct mov_t* mov); +size_t mov_write_tref(const struct mov_t* mov); +size_t mov_write_stbl(const struct mov_t* mov); +size_t mov_write_minf(const struct mov_t* mov); +size_t mov_write_mdia(const struct mov_t* mov); +size_t mov_write_trak(const struct mov_t* mov); +size_t mov_write_dops(const struct mov_t* mov); +size_t mov_write_udta(const struct mov_t* mov); + +uint32_t mov_build_stts(struct mov_track_t* track); +uint32_t mov_build_ctts(struct mov_track_t* track); +uint32_t mov_build_stco(struct mov_track_t* track); +void mov_apply_stco(struct mov_track_t* track); +void mov_apply_elst(struct mov_track_t *track); +void mov_apply_stts(struct mov_track_t* track); +void mov_apply_ctts(struct mov_track_t* track); +void mov_apply_stss(struct mov_track_t* track); +void mov_apply_elst_tfdt(struct mov_track_t *track); + +void mov_write_size(const struct mov_t* mov, uint64_t offset, size_t size); + +size_t mov_stco_size(const struct mov_track_t* track, uint64_t offset); + +int mov_fragment_read_next_moof(struct mov_t* mov); +int mov_fragment_seek_read_mfra(struct mov_t* mov); +int mov_fragment_seek(struct mov_t* mov, int64_t* timestamp); + +uint8_t mov_tag_to_object(uint32_t tag); +uint32_t mov_object_to_tag(uint8_t object); + +void mov_free_track(struct mov_track_t* track); +struct mov_track_t* mov_add_track(struct mov_t* mov); +struct mov_track_t* mov_find_track(const struct mov_t* mov, uint32_t track); +struct mov_track_t* mov_fetch_track(struct mov_t* mov, uint32_t track); // find and add +int mov_add_audio(struct mov_track_t* track, const struct mov_mvhd_t* mvhd, uint32_t timescale, uint8_t object, int channel_count, int bits_per_sample, int sample_rate, const void* extra_data, size_t extra_data_size); +int mov_add_video(struct mov_track_t* track, const struct mov_mvhd_t* mvhd, uint32_t timescale, uint8_t object, int width, int height, const void* extra_data, size_t extra_data_size); +int mov_add_subtitle(struct mov_track_t* track, const struct mov_mvhd_t* mvhd, uint32_t timescale, uint8_t object, const void* extra_data, size_t extra_data_size); + +#endif /* !_mov_internal_h_ */ diff --git a/MediaServer/libmov/source/mov-iods.c b/MediaServer/libmov/source/mov-iods.c new file mode 100644 index 0000000..4f884c4 --- /dev/null +++ b/MediaServer/libmov/source/mov-iods.c @@ -0,0 +1,90 @@ +#include "mov-internal.h" +#include +#include +#include + +// Table 1 - List of Class Tags for Descriptors (p31) +/* +0x10 MP4_IOD_Tag +0x11 MP4_OD_Tag +*/ + +// 7.2.2.2 BaseDescriptor (p32) +/* +abstract aligned(8) expandable(2^28-1) class BaseDescriptor : bit(8) tag=0 { +// empty. To be filled by classes extending this class. +} +*/ + +// 7.2.6.2 ObjectDescriptorBase (p42) +/* +abstract class ObjectDescriptorBase extends BaseDescriptor : bit(8) + tag=[ObjectDescrTag..InitialObjectDescrTag] { + // empty. To be filled by classes extending this class. +} +class ObjectDescriptor extends ObjectDescriptorBase : bit(8) tag=ObjectDescrTag { + bit(10) ObjectDescriptorID; + bit(1) URL_Flag; + const bit(5) reserved=0b1111.1; + if (URL_Flag) { + bit(8) URLlength; + bit(8) URLstring[URLlength]; + } else { + ES_Descriptor esDescr[1 .. 255]; + OCI_Descriptor ociDescr[0 .. 255]; + IPMP_DescriptorPointer ipmpDescrPtr[0 .. 255]; + IPMP_Descriptor ipmpDescr [0 .. 255]; + } + ExtensionDescriptor extDescr[0 .. 255]; +} +*/ + +// 7.2.6.4 InitialObjectDescriptor (p44) +/* +class InitialObjectDescriptor extends ObjectDescriptorBase : bit(8) + tag=InitialObjectDescrTag { + bit(10) ObjectDescriptorID; + bit(1) URL_Flag; + bit(1) includeInlineProfileLevelFlag; + const bit(4) reserved=0b1111; + if (URL_Flag) { + bit(8) URLlength; + bit(8) URLstring[URLlength]; + } else { + bit(8) ODProfileLevelIndication; + bit(8) sceneProfileLevelIndication; + bit(8) audioProfileLevelIndication; + bit(8) visualProfileLevelIndication; + bit(8) graphicsProfileLevelIndication; + ES_Descriptor esDescr[1 .. 255]; + OCI_Descriptor ociDescr[0 .. 255]; + IPMP_DescriptorPointer ipmpDescrPtr[0 .. 255]; + IPMP_Descriptor ipmpDescr [0 .. 255]; + IPMP_ToolListDescriptor toolListDescr[0 .. 1]; + } + ExtensionDescriptor extDescr[0 .. 255]; +} +*/ +size_t mov_write_iods(const struct mov_t* mov) +{ + size_t size = 12 /* full box */ + 12 /* InitialObjectDescriptor */; + + mov_buffer_w32(&mov->io, 24); /* size */ + mov_buffer_write(&mov->io, "iods", 4); + mov_buffer_w32(&mov->io, 0); /* version & flags */ + + mov_buffer_w8(&mov->io, 0x10); // ISO_MP4_IOD_Tag + mov_buffer_w8(&mov->io, (uint8_t)(0x80 | (7 >> 21))); + mov_buffer_w8(&mov->io, (uint8_t)(0x80 | (7 >> 14))); + mov_buffer_w8(&mov->io, (uint8_t)(0x80 | (7 >> 7))); + mov_buffer_w8(&mov->io, (uint8_t)(0x7F & 7)); + + mov_buffer_w16(&mov->io, 0x004f); // objectDescriptorId 1 + mov_buffer_w8(&mov->io, 0xff); // No OD capability required + mov_buffer_w8(&mov->io, 0xff); + mov_buffer_w8(&mov->io, 0xFF); + mov_buffer_w8(&mov->io, 0xFF); // no visual capability required + mov_buffer_w8(&mov->io, 0xff); + + return size; +} diff --git a/MediaServer/libmov/source/mov-ioutil.h b/MediaServer/libmov/source/mov-ioutil.h new file mode 100644 index 0000000..c48aaf0 --- /dev/null +++ b/MediaServer/libmov/source/mov-ioutil.h @@ -0,0 +1,123 @@ +#ifndef _mov_ioutil_h_ +#define _mov_ioutil_h_ + +#include "mov-buffer.h" + +struct mov_ioutil_t +{ + struct mov_buffer_t io; + void* param; + int error; +}; + +static inline int mov_buffer_error(const struct mov_ioutil_t* io) +{ + return io->error; +} + +static inline uint64_t mov_buffer_tell(const struct mov_ioutil_t* io) +{ + int64_t v; + v = io->io.tell(io->param); + if (v < 0) + ((struct mov_ioutil_t*)io)->error = -1; + return v; +} + +static inline void mov_buffer_seek(const struct mov_ioutil_t* io, int64_t offset) +{ +// if (0 == io->error) + ((struct mov_ioutil_t*)io)->error = io->io.seek(io->param, offset); +} + +static inline void mov_buffer_skip(struct mov_ioutil_t* io, uint64_t bytes) +{ + uint64_t offset; + if (0 == io->error) + { + offset = io->io.tell(io->param); + io->error = io->io.seek(io->param, offset + bytes); + } +} + +static inline void mov_buffer_read(struct mov_ioutil_t* io, void* data, uint64_t bytes) +{ + if (0 == io->error) + io->error = io->io.read(io->param, data, bytes); +} + +static inline void mov_buffer_write(const struct mov_ioutil_t* io, const void* data, uint64_t bytes) +{ + if (0 == io->error) + ((struct mov_ioutil_t*)io)->error = io->io.write(io->param, data, bytes); +} + +static inline uint8_t mov_buffer_r8(struct mov_ioutil_t* io) +{ + uint8_t v = 0; + mov_buffer_read(io, &v, 1); + return v; +} + +static inline uint16_t mov_buffer_r16(struct mov_ioutil_t* io) +{ + uint16_t v; + v = mov_buffer_r8(io); + v = (v << 8) | mov_buffer_r8(io); + return v; +} + +static inline uint32_t mov_buffer_r24(struct mov_ioutil_t* io) +{ + uint32_t v; + v = mov_buffer_r8(io); + v = (v << 16) | mov_buffer_r16(io); + return v; +} + +static inline uint32_t mov_buffer_r32(struct mov_ioutil_t* io) +{ + uint32_t v; + v = mov_buffer_r16(io); + v = (v << 16) | mov_buffer_r16(io); + return v; +} + +static inline uint64_t mov_buffer_r64(struct mov_ioutil_t* io) +{ + uint64_t v; + v = mov_buffer_r32(io); + v = (v << 32) | mov_buffer_r32(io); + return v; +} + +static inline void mov_buffer_w8(const struct mov_ioutil_t* io, uint8_t v) +{ + mov_buffer_write(io, &v, 1); +} + +static inline void mov_buffer_w16(const struct mov_ioutil_t* io, uint16_t v) +{ + mov_buffer_w8(io, (uint8_t)(v >> 8)); + mov_buffer_w8(io, (uint8_t)v); +} + +static inline void mov_buffer_w24(const struct mov_ioutil_t* io, uint32_t v) +{ + mov_buffer_w16(io, (uint16_t)(v >> 8)); + mov_buffer_w8(io, (uint8_t)v); +} + +static inline void mov_buffer_w32(const struct mov_ioutil_t* io, uint32_t v) +{ + mov_buffer_w16(io, (uint16_t)(v >> 16)); + mov_buffer_w16(io, (uint16_t)v); +} + +static inline void mov_buffer_w64(const struct mov_ioutil_t* io, uint64_t v) +{ + mov_buffer_w32(io, (uint32_t)(v >> 32)); + mov_buffer_w32(io, (uint32_t)v); +} + +#endif /* !_mov_ioutil_h_ */ diff --git a/MediaServer/libmov/source/mov-leva.c b/MediaServer/libmov/source/mov-leva.c new file mode 100644 index 0000000..f8ec649 --- /dev/null +++ b/MediaServer/libmov/source/mov-leva.c @@ -0,0 +1,35 @@ +#include "mov-internal.h" +#include + +// 8.8.13 Level Assignment Box (p77) +int mov_read_leva(struct mov_t* mov, const struct mov_box_t* box) +{ + unsigned int i, level_count; + unsigned int assignment_type; + + mov_buffer_r32(&mov->io); /* version & flags */ + level_count = mov_buffer_r8(&mov->io); /* level_count */ + for (i = 0; i < level_count; i++) + { + mov_buffer_r32(&mov->io); /* track_id */ + assignment_type = mov_buffer_r8(&mov->io); /* padding_flag & assignment_type */ + assignment_type &= 0x7F; // 7-bits + + if (0 == assignment_type) + { + mov_buffer_r32(&mov->io); /* grouping_type */ + } + else if (1 == assignment_type) + { + mov_buffer_r32(&mov->io); /* grouping_type */ + mov_buffer_r32(&mov->io); /* grouping_type_parameter */ + } + else if (4 == assignment_type) + { + mov_buffer_r32(&mov->io); /* sub_track_id */ + } + } + + (void)box; + return mov_buffer_error(&mov->io); +} diff --git a/MediaServer/libmov/source/mov-mdhd.c b/MediaServer/libmov/source/mov-mdhd.c new file mode 100644 index 0000000..26833dd --- /dev/null +++ b/MediaServer/libmov/source/mov-mdhd.c @@ -0,0 +1,76 @@ +#include "mov-internal.h" +#include + +// 8.4.2 Media Header Box (p35) +// Box Type: 'mdhd' +// Container: Media Box ('mdia') +// Mandatory: Yes +// Quantity: Exactly one + +/* +aligned(8) class MediaHeaderBox extends FullBox('mdhd', version, 0) { + if (version==1) { + unsigned int(64) creation_time; + unsigned int(64) modification_time; + unsigned int(32) timescale; + unsigned int(64) duration; + } else { // version==0 + unsigned int(32) creation_time; + unsigned int(32) modification_time; + unsigned int(32) timescale; + unsigned int(32) duration; + } + bit(1) pad = 0; + unsigned int(5)[3] language; // ISO-639-2/T language code + unsigned int(16) pre_defined = 0; +} +*/ +int mov_read_mdhd(struct mov_t* mov, const struct mov_box_t* box) +{ + uint32_t val; + struct mov_mdhd_t* mdhd = &mov->track->mdhd; + + mdhd->version = mov_buffer_r8(&mov->io); + mdhd->flags = mov_buffer_r24(&mov->io); + + if (1 == mdhd->version) + { + mdhd->creation_time = mov_buffer_r64(&mov->io); + mdhd->modification_time = mov_buffer_r64(&mov->io); + mdhd->timescale = mov_buffer_r32(&mov->io); + mdhd->duration = mov_buffer_r64(&mov->io); + } + else + { + assert(0 == mdhd->version); + mdhd->creation_time = mov_buffer_r32(&mov->io); + mdhd->modification_time = mov_buffer_r32(&mov->io); + mdhd->timescale = mov_buffer_r32(&mov->io); + mdhd->duration = mov_buffer_r32(&mov->io); + } + + val = mov_buffer_r32(&mov->io); + mdhd->language = (val >> 16) & 0x7FFF; + mdhd->pre_defined = val & 0xFFFF; + + (void)box; + return mov_buffer_error(&mov->io); +} + +size_t mov_write_mdhd(const struct mov_t* mov) +{ + const struct mov_mdhd_t* mdhd = &mov->track->mdhd; + + mov_buffer_w32(&mov->io, 32); /* size */ + mov_buffer_write(&mov->io, "mdhd", 4); + mov_buffer_w32(&mov->io, 0); /* version 1 & flags */ + + mov_buffer_w32(&mov->io, (uint32_t)mdhd->creation_time); /* creation_time */ + mov_buffer_w32(&mov->io, (uint32_t)mdhd->modification_time); /* modification_time */ + mov_buffer_w32(&mov->io, mdhd->timescale); /* timescale */ + mov_buffer_w32(&mov->io, (uint32_t)mdhd->duration); /* duration */ + + mov_buffer_w16(&mov->io, (uint16_t)mdhd->language); /* ISO-639-2/T language code */ + mov_buffer_w16(&mov->io, 0); /* pre_defined (quality) */ + return 32; +} diff --git a/MediaServer/libmov/source/mov-mehd.c b/MediaServer/libmov/source/mov-mehd.c new file mode 100644 index 0000000..dcb7387 --- /dev/null +++ b/MediaServer/libmov/source/mov-mehd.c @@ -0,0 +1,33 @@ +#include "mov-internal.h" +#include +#include +#include +#include + +// 8.8.2 Movie Extends Header Box (p68) +int mov_read_mehd(struct mov_t* mov, const struct mov_box_t* box) +{ + unsigned int version; + uint64_t fragment_duration; + version = mov_buffer_r8(&mov->io); /* version */ + mov_buffer_r24(&mov->io); /* flags */ + + if (1 == version) + fragment_duration = mov_buffer_r64(&mov->io); /* fragment_duration*/ + else + fragment_duration = mov_buffer_r32(&mov->io); /* fragment_duration*/ + + (void)box; + //assert(fragment_duration <= mov->mvhd.duration); + return mov_buffer_error(&mov->io); +} + +size_t mov_write_mehd(const struct mov_t* mov) +{ + mov_buffer_w32(&mov->io, 20); /* size */ + mov_buffer_write(&mov->io, "mehd", 4); + mov_buffer_w8(&mov->io, 1); /* version */ + mov_buffer_w24(&mov->io, 0); /* flags */ + mov_buffer_w64(&mov->io, mov->mvhd.duration); // 0 ? + return 20; +} diff --git a/MediaServer/libmov/source/mov-mfhd.c b/MediaServer/libmov/source/mov-mfhd.c new file mode 100644 index 0000000..8adfac9 --- /dev/null +++ b/MediaServer/libmov/source/mov-mfhd.c @@ -0,0 +1,22 @@ +#include "mov-internal.h" +#include +#include +#include + +// 8.8.5 Movie Fragment Header Box (p70) +int mov_read_mfhd(struct mov_t* mov, const struct mov_box_t* box) +{ + (void)box; + mov_buffer_r32(&mov->io); /* version & flags */ + mov_buffer_r32(&mov->io); /* sequence_number */ + return mov_buffer_error(&mov->io); +} + +size_t mov_write_mfhd(const struct mov_t* mov, uint32_t fragment) +{ + mov_buffer_w32(&mov->io, 16); /* size */ + mov_buffer_write(&mov->io, "mfhd", 4); + mov_buffer_w32(&mov->io, 0); /* version & flags */ + mov_buffer_w32(&mov->io, fragment); /* sequence_number */ + return 16; +} diff --git a/MediaServer/libmov/source/mov-minf.c b/MediaServer/libmov/source/mov-minf.c new file mode 100644 index 0000000..74425ca --- /dev/null +++ b/MediaServer/libmov/source/mov-minf.c @@ -0,0 +1,166 @@ +#include "mov-internal.h" +#include + +int mov_read_vmhd(struct mov_t* mov, const struct mov_box_t* box) +{ + mov_buffer_r8(&mov->io); /* version */ + mov_buffer_r24(&mov->io); /* flags */ + mov_buffer_r16(&mov->io); /* graphicsmode */ + // template unsigned int(16)[3] opcolor = {0, 0, 0}; + mov_buffer_skip(&mov->io, 6); + + (void)box; + return 0; +} + +int mov_read_smhd(struct mov_t* mov, const struct mov_box_t* box) +{ + mov_buffer_r8(&mov->io); /* version */ + mov_buffer_r24(&mov->io); /* flags */ + mov_buffer_r16(&mov->io); /* balance */ + //const unsigned int(16) reserved = 0; + mov_buffer_skip(&mov->io, 2); + + (void)box; + return 0; +} + +int mov_read_nmhd(struct mov_t* mov, const struct mov_box_t* box) +{ + mov_buffer_r8(&mov->io); /* version */ + mov_buffer_r24(&mov->io); /* flags */ + (void)box; + return 0; +} + +// https://developer.apple.com/library/archive/documentation/QuickTime/QTFF/QTFFChap2/qtff2.html#//apple_ref/doc/uid/TP40000939-CH204-25675 +/* +Size: A 32-bit integer that specifies the number of bytes in this base media info atom. +Type: A 32-bit integer that identifies the atom type; this field must be set to 'gmin'. +Version: A 1-byte specification of the version of this base media information header atom. +Flags: A 3-byte space for base media information flags. Set this field to 0. +Graphics mode: A 16-bit integer that specifies the transfer mode. The transfer mode specifies which Boolean operation QuickDraw should perform when drawing or transferring an image from one location to another. See Graphics Modes for more information about graphics modes supported by QuickTime. +Opcolor: Three 16-bit values that specify the red, green, and blue colors for the transfer mode operation indicated in the graphics mode field. +Balance: A 16-bit integer that specifies the sound balance of this media. Sound balance is the setting that controls the mix of sound between the two speakers of a computer. This field is normally set to 0. See Balance for more information about balance values. +Reserved: Reserved for use by Apple. A 16-bit integer. Set this field to 0 +*/ +int mov_read_gmin(struct mov_t* mov, const struct mov_box_t* box) +{ + mov_buffer_r8(&mov->io); /* version */ + mov_buffer_r24(&mov->io); /* flags */ + mov_buffer_r16(&mov->io); /* graphics mode */ + mov_buffer_r16(&mov->io); /* opcolor red*/ + mov_buffer_r16(&mov->io); /* opcolor green*/ + mov_buffer_r16(&mov->io); /* opcolor blue*/ + mov_buffer_r16(&mov->io); /* balance */ + mov_buffer_r16(&mov->io); /* reserved */ + + (void)box; + return 0; +} + +// https://developer.apple.com/library/archive/documentation/QuickTime/QTFF/QTFFChap3/qtff3.html#//apple_ref/doc/uid/TP40000939-CH205-SW90 +/* +Size:A 32-bit integer that specifies the number of bytes in this text media information atom. +Type:A 32-bit integer that identifies the atom type; this field must be set to 'text'. +Matrix structure:A matrix structure associated with this text media +*/ +int mov_read_text(struct mov_t* mov, const struct mov_box_t* box) +{ + int i; + // Matrix structure + for (i = 0; i < 9; i++) + mov_buffer_r32(&mov->io); + + (void)box; + return 0; +} + +int mov_read_chpl(struct mov_t* mov, const struct mov_box_t* box) +{ + int i, count, len; + mov_buffer_r8(&mov->io); /* version */ + mov_buffer_r24(&mov->io); /* flags */ + mov_buffer_skip(&mov->io, 4); /* unknown*/ + + count = mov_buffer_r8(&mov->io); /* count */ + for (i = 0; i < count; i++) + { + mov_buffer_r64(&mov->io); /* timestamp */ + len = mov_buffer_r8(&mov->io); /* title size */ + mov_buffer_skip(&mov->io, len); /* title */ + } + + (void)box; + return 0; +} + +size_t mov_write_vmhd(const struct mov_t* mov) +{ + mov_buffer_w32(&mov->io, 20); /* size (always 0x14) */ + mov_buffer_write(&mov->io, "vmhd", 4); + mov_buffer_w32(&mov->io, 0x01); /* version & flags */ + mov_buffer_w64(&mov->io, 0); /* reserved (graphics mode = copy) */ + return 20; +} + +size_t mov_write_smhd(const struct mov_t* mov) +{ + mov_buffer_w32(&mov->io, 16); /* size */ + mov_buffer_write(&mov->io, "smhd", 4); + mov_buffer_w32(&mov->io, 0); /* version & flags */ + mov_buffer_w16(&mov->io, 0); /* reserved (balance, normally = 0) */ + mov_buffer_w16(&mov->io, 0); /* reserved */ + return 16; +} + +size_t mov_write_nmhd(const struct mov_t* mov) +{ + mov_buffer_w32(&mov->io, 12); /* size */ + mov_buffer_write(&mov->io, "nmhd", 4); + mov_buffer_w32(&mov->io, 0); /* version & flags */ + return 12; +} + +size_t mov_write_gmhd(const struct mov_t* mov) +{ + mov_buffer_w32(&mov->io, 8 + 24 + 44); /* size */ + mov_buffer_write(&mov->io, "gmhd", 4); + + mov_buffer_w32(&mov->io, 24); /* size */ + mov_buffer_write(&mov->io, "gmin", 4); + mov_buffer_w32(&mov->io, 0); /* version & flags */ + mov_buffer_w16(&mov->io, 0x40); /* graphics mode */ + mov_buffer_w16(&mov->io, 0x8000); /* opcolor red*/ + mov_buffer_w16(&mov->io, 0x8000); /* opcolor green*/ + mov_buffer_w16(&mov->io, 0x8000); /* opcolor blue*/ + mov_buffer_w16(&mov->io, 0); /* balance */ + mov_buffer_w16(&mov->io, 0); /* reserved */ + + mov_buffer_w32(&mov->io, 44); /* size */ + mov_buffer_write(&mov->io, "text", 4); + mov_buffer_w32(&mov->io, 0x00010000); /* u */ + mov_buffer_w32(&mov->io, 0); + mov_buffer_w32(&mov->io, 0); + mov_buffer_w32(&mov->io, 0); /* v */ + mov_buffer_w32(&mov->io, 0x00010000); + mov_buffer_w32(&mov->io, 0); + mov_buffer_w32(&mov->io, 0); /* w */ + mov_buffer_w32(&mov->io, 0); + mov_buffer_w32(&mov->io, 0x40000000); + + return 8 + 24 + 44; +} + +/* +ISO/IEC 14496-12:2015(E) 12.6.2 Subtitle media header (p185) + aligned(8) class SubtitleMediaHeaderBox extends FullBox ('sthd', version = 0, flags = 0){ +} +*/ +size_t mov_write_sthd(const struct mov_t* mov) +{ + mov_buffer_w32(&mov->io, 12); /* size */ + mov_buffer_write(&mov->io, "sthd", 4); + mov_buffer_w32(&mov->io, 0); /* version & flags */ + return 12; +} diff --git a/MediaServer/libmov/source/mov-mvhd.c b/MediaServer/libmov/source/mov-mvhd.c new file mode 100644 index 0000000..f0035f1 --- /dev/null +++ b/MediaServer/libmov/source/mov-mvhd.c @@ -0,0 +1,125 @@ +#include "mov-internal.h" +#include + +// ISO/IEC 14496-12:2012(E) +// 8.2.2.1 Movie Header Box (p30) +// Box Type : 'mvhd' +// Container : Movie Box('moov') +// Mandatory : Yes +// Quantity : Exactly one + +/* +aligned(8) class MovieHeaderBox extends FullBox('mvhd', version, 0) { + if (version==1) { + unsigned int(64) creation_time; + unsigned int(64) modification_time; + unsigned int(32) timescale; + unsigned int(64) duration; + } else { // version==0 + unsigned int(32) creation_time; + unsigned int(32) modification_time; + unsigned int(32) timescale; + unsigned int(32) duration; + } + template int(32) rate = 0x00010000; // typically 1.0 + template int(16) volume = 0x0100; // typically, full volume + const bit(16) reserved = 0; + const unsigned int(32)[2] reserved = 0; + template int(32)[9] matrix = { + 0x00010000,0,0,0,0x00010000,0,0,0,0x40000000 + }; // Unity matrix + bit(32)[6] pre_defined = 0; + unsigned int(32) next_track_ID; +} +*/ +int mov_read_mvhd(struct mov_t* mov, const struct mov_box_t* box) +{ + int i; + struct mov_mvhd_t* mvhd = &mov->mvhd; + + mvhd->version = mov_buffer_r8(&mov->io); + mvhd->flags = mov_buffer_r24(&mov->io); + + if (1 == mvhd->version) + { + mvhd->creation_time = mov_buffer_r64(&mov->io); + mvhd->modification_time = mov_buffer_r64(&mov->io); + mvhd->timescale = mov_buffer_r32(&mov->io); + mvhd->duration = mov_buffer_r64(&mov->io); + } + else + { + assert(0 == mvhd->version); + mvhd->creation_time = mov_buffer_r32(&mov->io); + mvhd->modification_time = mov_buffer_r32(&mov->io); + mvhd->timescale = mov_buffer_r32(&mov->io); + mvhd->duration = mov_buffer_r32(&mov->io); + } + + mvhd->rate = mov_buffer_r32(&mov->io); + mvhd->volume = (uint16_t)mov_buffer_r16(&mov->io); + //mvhd->reserved = mov_buffer_r16(&mov->io); + //mvhd->reserved2[0] = mov_buffer_r32(&mov->io); + //mvhd->reserved2[1] = mov_buffer_r32(&mov->io); + mov_buffer_skip(&mov->io, 10); + for (i = 0; i < 9; i++) + mvhd->matrix[i] = mov_buffer_r32(&mov->io); +#if 0 + for (i = 0; i < 6; i++) + mvhd->pre_defined[i] = mov_buffer_r32(&mov->io); +#else + mov_buffer_r32(&mov->io); /* preview time */ + mov_buffer_r32(&mov->io); /* preview duration */ + mov_buffer_r32(&mov->io); /* poster time */ + mov_buffer_r32(&mov->io); /* selection time */ + mov_buffer_r32(&mov->io); /* selection duration */ + mov_buffer_r32(&mov->io); /* current time */ +#endif + mvhd->next_track_ID = mov_buffer_r32(&mov->io); + + (void)box; + return 0; +} + +size_t mov_write_mvhd(const struct mov_t* mov) +{ +// int rotation = 0; // 90/180/270 + const struct mov_mvhd_t* mvhd = &mov->mvhd; + + mov_buffer_w32(&mov->io, 108); /* size */ + mov_buffer_write(&mov->io, "mvhd", 4); + mov_buffer_w32(&mov->io, 0); /* version & flags */ + + mov_buffer_w32(&mov->io, (uint32_t)mvhd->creation_time); /* creation_time */ + mov_buffer_w32(&mov->io, (uint32_t)mvhd->modification_time); /* modification_time */ + mov_buffer_w32(&mov->io, mvhd->timescale); /* timescale */ + mov_buffer_w32(&mov->io, (uint32_t)mvhd->duration); /* duration */ + + mov_buffer_w32(&mov->io, 0x00010000); /* rate 1.0 */ + mov_buffer_w16(&mov->io, 0x0100); /* volume 1.0 = normal */ + mov_buffer_w16(&mov->io, 0); /* reserved */ + mov_buffer_w32(&mov->io, 0); /* reserved */ + mov_buffer_w32(&mov->io, 0); /* reserved */ + + // matrix + mov_buffer_w32(&mov->io, 0x00010000); /* u */ + mov_buffer_w32(&mov->io, 0); + mov_buffer_w32(&mov->io, 0); + mov_buffer_w32(&mov->io, 0); /* v */ + mov_buffer_w32(&mov->io, 0x00010000); + mov_buffer_w32(&mov->io, 0); + mov_buffer_w32(&mov->io, 0); /* w */ + mov_buffer_w32(&mov->io, 0); + mov_buffer_w32(&mov->io, 0x40000000); + + mov_buffer_w32(&mov->io, 0); /* reserved (preview time) */ + mov_buffer_w32(&mov->io, 0); /* reserved (preview duration) */ + mov_buffer_w32(&mov->io, 0); /* reserved (poster time) */ + mov_buffer_w32(&mov->io, 0); /* reserved (selection time) */ + mov_buffer_w32(&mov->io, 0); /* reserved (selection duration) */ + mov_buffer_w32(&mov->io, 0); /* reserved (current time) */ + + mov_buffer_w32(&mov->io, mvhd->next_track_ID); /* Next track id */ + + return 108; +} diff --git a/MediaServer/libmov/source/mov-opus.c b/MediaServer/libmov/source/mov-opus.c new file mode 100644 index 0000000..4f9f127 --- /dev/null +++ b/MediaServer/libmov/source/mov-opus.c @@ -0,0 +1,77 @@ +#include "mov-internal.h" +#include +#include +#include +#include + +// http://www.opus-codec.org/docs/opus_in_isobmff.html +// 4.3.2 Opus Specific Box +/* +class ChannelMappingTable (unsigned int(8) OutputChannelCount){ + unsigned int(8) StreamCount; + unsigned int(8) CoupledCount; + unsigned int(8 * OutputChannelCount) ChannelMapping; +} + +aligned(8) class OpusSpecificBox extends Box('dOps'){ + unsigned int(8) Version; + unsigned int(8) OutputChannelCount; + unsigned int(16) PreSkip; + unsigned int(32) InputSampleRate; + signed int(16) OutputGain; + unsigned int(8) ChannelMappingFamily; + if (ChannelMappingFamily != 0) { + ChannelMappingTable(OutputChannelCount); + } +} +*/ + +int mov_read_dops(struct mov_t* mov, const struct mov_box_t* box) +{ + struct mov_track_t* track = mov->track; + struct mov_sample_entry_t* entry = track->stsd.current; + if(box->size >= 10) + { + if (entry->extra_data_size < box->size + 8) + { + void* p = realloc(entry->extra_data, (size_t)box->size + 8); + if (NULL == p) return -ENOMEM; + entry->extra_data = p; + } + + memcpy(entry->extra_data, "OpusHead", 8); + entry->extra_data[8] = 1; // OpusHead version + mov_buffer_r8(&mov->io); // version 0 + entry->extra_data[9] = mov_buffer_r8(&mov->io); // channel + entry->extra_data[11] = mov_buffer_r8(&mov->io); // PreSkip (MSB -> LSB) + entry->extra_data[10] = mov_buffer_r8(&mov->io); + entry->extra_data[15] = mov_buffer_r8(&mov->io); // InputSampleRate (LSB -> MSB) + entry->extra_data[14] = mov_buffer_r8(&mov->io); + entry->extra_data[13] = mov_buffer_r8(&mov->io); + entry->extra_data[12] = mov_buffer_r8(&mov->io); + entry->extra_data[17] = mov_buffer_r8(&mov->io); // OutputGain (LSB -> MSB) + entry->extra_data[16] = mov_buffer_r8(&mov->io); + mov_buffer_read(&mov->io, entry->extra_data + 18, (size_t)box->size - 10); + entry->extra_data_size = (int)box->size + 8; + } + return mov_buffer_error(&mov->io); +} + +size_t mov_write_dops(const struct mov_t* mov) +{ + const struct mov_track_t* track = mov->track; + const struct mov_sample_entry_t* entry = track->stsd.current; + if (entry->extra_data_size < 18) + return 0; + + assert(0 == memcmp(entry->extra_data, "OpusHead", 8)); + mov_buffer_w32(&mov->io, entry->extra_data_size); /* size */ + mov_buffer_write(&mov->io, "dOps", 4); + mov_buffer_w8(&mov->io, 0); // The Version field shall be set to 0. + mov_buffer_w8(&mov->io, entry->extra_data[9]); // channel count + mov_buffer_w16(&mov->io, (entry->extra_data[11]<<8) | entry->extra_data[10]); // PreSkip (LSB -> MSB) + mov_buffer_w32(&mov->io, (entry->extra_data[15]<<8) | (entry->extra_data[14]<<8) | (entry->extra_data[13]<<8) | entry->extra_data[12]); // InputSampleRate (LSB -> MSB) + mov_buffer_w16(&mov->io, (entry->extra_data[17]<<8) | entry->extra_data[16]); // OutputGain (LSB -> MSB) + mov_buffer_write(&mov->io, entry->extra_data + 18, entry->extra_data_size - 18); + return entry->extra_data_size; +} diff --git a/MediaServer/libmov/source/mov-reader.c b/MediaServer/libmov/source/mov-reader.c new file mode 100644 index 0000000..7ade5a6 --- /dev/null +++ b/MediaServer/libmov/source/mov-reader.c @@ -0,0 +1,687 @@ +#include "mov-reader.h" +#include "mov-internal.h" +#include +#include +#include +#include +#include +#include + +#define MOV_NULL MOV_TAG(0, 0, 0, 0) + +#define AV_TRACK_TIMEBASE 1000 + +//#define MOV_READER_BOX_TREE 1 +//#define MOV_READER_FMP4_FAST 1 + +#define MOV_READER_FLAG_FMP4_FAST 0x01 + +struct mov_reader_t +{ + int flags; + int have_read_mfra; + + struct mov_t mov; +}; + +#define MOV_READER_FROM_MOV(ptr) ((struct mov_reader_t*)((char*)(ptr)-(ptrdiff_t)(&((struct mov_reader_t*)0)->mov))) + +struct mov_parse_t +{ + uint32_t type; + uint32_t parent; + int(*parse)(struct mov_t* mov, const struct mov_box_t* box); +}; + +static int mov_stss_seek(struct mov_track_t* track, int64_t *timestamp); +static int mov_sample_seek(struct mov_track_t* track, int64_t timestamp); + +// 8.1.1 Media Data Box (p28) +static int mov_read_mdat(struct mov_t* mov, const struct mov_box_t* box) +{ + mov_buffer_skip(&mov->io, box->size); + return mov_buffer_error(&mov->io); +} + +// 8.1.2 Free Space Box (p28) +static int mov_read_free(struct mov_t* mov, const struct mov_box_t* box) +{ + // Container: File or other box + mov_buffer_skip(&mov->io, box->size); + return mov_buffer_error(&mov->io); +} + +//static struct mov_sample_entry_t* mov_track_stsd_find(struct mov_track_t* track, uint32_t sample_description_index) +//{ +// if (sample_description_index > 0 && sample_description_index <= track->stsd.entry_count) +// return &track->stsd.entries[sample_description_index-1]; +// return NULL; +//} + +static int mov_index_build(struct mov_track_t* track) +{ + void* p; + uint32_t i, j; + struct mov_stbl_t* stbl = &track->stbl; + + if (stbl->stss_count > 0 || MOV_VIDEO != track->handler_type) + return 0; + + for (i = 0; i < track->sample_count; i++) + { + if (track->samples[i].flags & MOV_AV_FLAG_KEYFREAME) + ++stbl->stss_count; + } + + p = realloc(stbl->stss, sizeof(stbl->stss[0]) * stbl->stss_count); + if (!p) return -ENOMEM; + stbl->stss = p; + + for (j = i = 0; i < track->sample_count && j < stbl->stss_count; i++) + { + if (track->samples[i].flags & MOV_AV_FLAG_KEYFREAME) + stbl->stss[j++] = i + 1; // uint32_t sample_number, start from 1 + } + assert(j == stbl->stss_count); + return 0; +} + +// 8.3.1 Track Box (p31) +// Box Type : 'trak' +// Container : Movie Box('moov') +// Mandatory : Yes +// Quantity : One or more +static int mov_read_trak(struct mov_t* mov, const struct mov_box_t* box) +{ + int r; + + mov->track = NULL; + r = mov_reader_box(mov, box); + if (0 == r) + { + mov->track->tfdt_dts = 0; + if (mov->track->sample_count > 0) + { + mov_apply_stco(mov->track); + mov_apply_elst(mov->track); + mov_apply_stts(mov->track); + mov_apply_ctts(mov->track); + mov_apply_stss(mov->track); + + mov->track->tfdt_dts = mov->track->samples[mov->track->sample_count - 1].dts; + } + } + + return r; +} + +static int mov_read_dref(struct mov_t* mov, const struct mov_box_t* box) +{ + uint32_t i, entry_count; + mov_buffer_r8(&mov->io); /* version */ + mov_buffer_r24(&mov->io); /* flags */ + entry_count = mov_buffer_r32(&mov->io); + + for (i = 0; i < entry_count; i++) + { + uint32_t size = mov_buffer_r32(&mov->io); + /*uint32_t type = */mov_buffer_r32(&mov->io); + /*uint32_t vern = */mov_buffer_r32(&mov->io); /* version + flags */ + mov_buffer_skip(&mov->io, size-12); + } + + (void)box; + return 0; +} + +static int mov_read_btrt(struct mov_t* mov, const struct mov_box_t* box) +{ + // ISO/IEC 14496-15:2010(E) + // 5.3.4 AVC Video Stream Definition (p19) + mov_buffer_r32(&mov->io); /* bufferSizeDB */ + mov_buffer_r32(&mov->io); /* maxBitrate */ + mov_buffer_r32(&mov->io); /* avgBitrate */ + (void)box; + return 0; +} + +static int mov_read_uuid(struct mov_t* mov, const struct mov_box_t* box) +{ + uint8_t usertype[16] = { 0 }; + if(box->size > 16) + { + mov_buffer_read(&mov->io, usertype, sizeof(usertype)); + mov_buffer_skip(&mov->io, box->size - 16); + } + return mov_buffer_error(&mov->io); +} + +static int mov_read_moof(struct mov_t* mov, const struct mov_box_t* box) +{ + // 8.8.7 Track Fragment Header Box (p71) + // If base-data-offset-present not provided and if the default-base-is-moof flag is not set, + // the base-data-offset for the first track in the movie fragment is the position of + // the first byte of the enclosing Movie Fragment Box, for second and subsequent track fragments, + // the default is the end of the data defined by the preceding track fragment. + mov->moof_offset = mov->implicit_offset = mov_buffer_tell(&mov->io) - 8 /*box size */; + return mov_reader_box(mov, box); +} + +static int mov_read_mfra(struct mov_t* mov, const struct mov_box_t* box) +{ + int r; + struct mov_reader_t* reader; + reader = MOV_READER_FROM_MOV(mov); + r = mov_reader_box(mov, box); + reader->have_read_mfra = 1; + return r; +} + +// 8.8.11 Movie Fragment Random Access Offset Box (p75) +static int mov_read_mfro(struct mov_t* mov, const struct mov_box_t* box) +{ + (void)box; + mov_buffer_r32(&mov->io); /* version & flags */ + mov->mfro = mov_buffer_r32(&mov->io); /* size */ + return mov_buffer_error(&mov->io); +} + +int mov_reader_root(struct mov_t* mov) +{ + struct mov_box_t box; + + box.type = MOV_ROOT; + box.size = UINT64_MAX; +#if defined(DEBUG) || defined(_DEBUG) + box.level = 0; +#endif + + return mov_reader_box(mov, &box); +} + +static int mov_read_default(struct mov_t* mov, const struct mov_box_t* box) +{ + return mov_reader_box(mov, box); +} + +static struct mov_parse_t s_mov_parse_table[] = { + { MOV_TAG('a', 'v', '1', 'C'), MOV_NULL, mov_read_extra }, // av1-isobmff + { MOV_TAG('a', 'v', 'c', 'C'), MOV_NULL, mov_read_extra }, // ISO/IEC 14496-15:2010(E) avcC + { MOV_TAG('b', 't', 'r', 't'), MOV_NULL, mov_read_btrt }, // ISO/IEC 14496-15:2010(E) 5.3.4.1.1 Definition + { MOV_TAG('c', 'h', 'p', 'l'), MOV_STBL, mov_read_chpl }, // chapter title + { MOV_TAG('c', 'o', '6', '4'), MOV_STBL, mov_read_stco }, + { MOV_TAG('C', 'o', 'L', 'L'), MOV_STBL, mov_read_coll }, + { MOV_TAG('c', 't', 't', 's'), MOV_STBL, mov_read_ctts }, + { MOV_TAG('c', 's', 'l', 'g'), MOV_STBL, mov_read_cslg }, + { MOV_TAG('d', 'i', 'n', 'f'), MOV_MINF, mov_read_default }, + { MOV_TAG('d', 'O', 'p', 's'), MOV_NULL, mov_read_dops }, + { MOV_TAG('d', 'r', 'e', 'f'), MOV_DINF, mov_read_dref }, + { MOV_TAG('e', 'd', 't', 's'), MOV_TRAK, mov_read_default }, + { MOV_TAG('e', 'l', 's', 't'), MOV_EDTS, mov_read_elst }, + { MOV_TAG('e', 's', 'd', 's'), MOV_NULL, mov_read_esds }, // ISO/IEC 14496-14:2003(E) mp4a/mp4v/mp4s + { MOV_TAG('f', 'r', 'e', 'e'), MOV_NULL, mov_read_free }, + { MOV_TAG('f', 't', 'y', 'p'), MOV_ROOT, mov_read_ftyp }, + { MOV_TAG('g', 'm', 'i', 'n'), MOV_GMHD, mov_read_gmin }, // Apple QuickTime gmin + { MOV_TAG('g', 'm', 'h', 'd'), MOV_MINF, mov_read_default }, // Apple QuickTime gmhd + { MOV_TAG('h', 'd', 'l', 'r'), MOV_MDIA, mov_read_hdlr }, // Apple QuickTime minf also has hdlr + { MOV_TAG('h', 'v', 'c', 'C'), MOV_NULL, mov_read_extra }, // ISO/IEC 14496-15:2014 hvcC + { MOV_TAG('l', 'e', 'v', 'a'), MOV_MVEX, mov_read_leva }, + { MOV_TAG('m', 'd', 'a', 't'), MOV_ROOT, mov_read_mdat }, + { MOV_TAG('m', 'd', 'h', 'd'), MOV_MDIA, mov_read_mdhd }, + { MOV_TAG('m', 'd', 'i', 'a'), MOV_TRAK, mov_read_default }, + { MOV_TAG('m', 'e', 'h', 'd'), MOV_MVEX, mov_read_mehd }, + { MOV_TAG('m', 'f', 'h', 'd'), MOV_MOOF, mov_read_mfhd }, + { MOV_TAG('m', 'f', 'r', 'a'), MOV_ROOT, mov_read_mfra }, + { MOV_TAG('m', 'f', 'r', 'o'), MOV_MFRA, mov_read_mfro }, + { MOV_TAG('m', 'i', 'n', 'f'), MOV_MDIA, mov_read_default }, + { MOV_TAG('m', 'o', 'o', 'v'), MOV_ROOT, mov_read_default }, + { MOV_TAG('m', 'o', 'o', 'f'), MOV_ROOT, mov_read_moof }, + { MOV_TAG('m', 'v', 'e', 'x'), MOV_MOOV, mov_read_default }, + { MOV_TAG('m', 'v', 'h', 'd'), MOV_MOOV, mov_read_mvhd }, + { MOV_TAG('n', 'm', 'h', 'd'), MOV_MINF, mov_read_nmhd }, // ISO/IEC 14496-12:2015(E) 8.4.5.2 Null Media Header Box (p45) + { MOV_TAG('p', 'a', 's', 'p'), MOV_NULL, mov_read_pasp }, + { MOV_TAG('s', 'i', 'd', 'x'), MOV_ROOT, mov_read_sidx }, + { MOV_TAG('s', 'k', 'i', 'p'), MOV_NULL, mov_read_free }, + { MOV_TAG('S', 'm', 'D', 'm'), MOV_MINF, mov_read_smdm }, + { MOV_TAG('s', 'm', 'h', 'd'), MOV_MINF, mov_read_smhd }, + { MOV_TAG('s', 't', 'b', 'l'), MOV_MINF, mov_read_default }, + { MOV_TAG('s', 't', 'c', 'o'), MOV_STBL, mov_read_stco }, +// { MOV_TAG('s', 't', 'h', 'd'), MOV_MINF, mov_read_default }, // ISO/IEC 14496-12:2015(E) 12.6.2 Subtitle media header (p185) + { MOV_TAG('s', 't', 's', 'c'), MOV_STBL, mov_read_stsc }, + { MOV_TAG('s', 't', 's', 'd'), MOV_STBL, mov_read_stsd }, + { MOV_TAG('s', 't', 's', 's'), MOV_STBL, mov_read_stss }, + { MOV_TAG('s', 't', 's', 'z'), MOV_STBL, mov_read_stsz }, + { MOV_TAG('s', 't', 't', 's'), MOV_STBL, mov_read_stts }, + { MOV_TAG('s', 't', 'z', '2'), MOV_STBL, mov_read_stz2 }, + { MOV_TAG('t', 'e', 'x', 't'), MOV_GMHD, mov_read_text }, + { MOV_TAG('t', 'f', 'd', 't'), MOV_TRAF, mov_read_tfdt }, + { MOV_TAG('t', 'f', 'h', 'd'), MOV_TRAF, mov_read_tfhd }, + { MOV_TAG('t', 'f', 'r', 'a'), MOV_MFRA, mov_read_tfra }, + { MOV_TAG('t', 'k', 'h', 'd'), MOV_TRAK, mov_read_tkhd }, + { MOV_TAG('t', 'r', 'a', 'k'), MOV_MOOV, mov_read_trak }, + { MOV_TAG('t', 'r', 'e', 'x'), MOV_MVEX, mov_read_trex }, + { MOV_TAG('t', 'r', 'a', 'f'), MOV_MOOF, mov_read_default }, + { MOV_TAG('t', 'r', 'u', 'n'), MOV_TRAF, mov_read_trun }, + { MOV_TAG('u', 'd', 't', 'a'), MOV_MOOV, mov_read_udta }, + { MOV_TAG('u', 'u', 'i', 'd'), MOV_NULL, mov_read_uuid }, + { MOV_TAG('v', 'm', 'h', 'd'), MOV_MINF, mov_read_vmhd }, + { MOV_TAG('v', 'p', 'c', 'C'), MOV_NULL, mov_read_vpcc }, + { MOV_TAG('d', 'e', 'c', '3'), MOV_NULL, mov_read_extra }, // dolby EC-3 + + { 0, 0, NULL } // last +}; + +int mov_reader_box(struct mov_t* mov, const struct mov_box_t* parent) +{ + int i; + uint64_t bytes = 0; + struct mov_box_t box; + struct mov_reader_t* reader; + int (*parse)(struct mov_t* mov, const struct mov_box_t* box); + + reader = MOV_READER_FROM_MOV(mov); + while (bytes + 8 < parent->size && 0 == mov_buffer_error(&mov->io)) + { + uint64_t n = 8; + box.size = mov_buffer_r32(&mov->io); + box.type = mov_buffer_r32(&mov->io); + +#if defined(MOV_READER_BOX_TREE) && !defined(NDEBUG) + box.level = parent->level + 1; + for (i = 0; i < parent->level; i++) + printf("\t"); + printf("%c%c%c%c, size: %d\n", (char)(box.type >> 24), (char)(box.type >> 16), (char)(box.type >> 8), (char)box.type, (int)box.size); +#endif + + if (1 == box.size) + { + // unsigned int(64) large size + box.size = mov_buffer_r64(&mov->io); + n += 8; + } + else if (0 == box.size) + { + if (0 == box.type) + return 0; // all done + box.size = UINT64_MAX; + } + + if (UINT64_MAX == box.size) + { + bytes = parent->size; + } + else + { + bytes += box.size; + box.size -= n; + } + + if (bytes > parent->size) + return -1; + + for (i = 0, parse = NULL; s_mov_parse_table[i].type && !parse; i++) + { + if (s_mov_parse_table[i].type == box.type) + { + // Apple QuickTime minf also has hdlr + if(!s_mov_parse_table[i].parent || MOV_ROOT == parent->type || s_mov_parse_table[i].parent == parent->type) + parse = s_mov_parse_table[i].parse; + } + } + + if (NULL == parse) + { + mov_buffer_skip(&mov->io, box.size); + } + else + { + int r; + uint64_t pos, pos2; + pos = mov_buffer_tell(&mov->io); + r = parse(mov, &box); + assert(0 == r || mov_buffer_error(&mov->io)); + if (0 != r) return r; + pos2 = mov_buffer_tell(&mov->io); + assert(pos2 - pos == box.size); + mov_buffer_skip(&mov->io, box.size - (pos2 - pos)); + } + + // fmp4: read one-fragment only + if ((reader->flags & MOV_READER_FLAG_FMP4_FAST) && MOV_TAG('m', 'o', 'o', 'f') == box.type) + { + if (!reader->have_read_mfra) + { + mov_fragment_seek_read_mfra(mov); + reader->have_read_mfra = 1; // force, seek once only + } + + // skip fast mode, fallback to read all + if(mov->mfro > 0) + break; + } + } + + return mov_buffer_error(&mov->io) ? -1 : 0; +} + +static int mov_reader_init(struct mov_reader_t* reader) +{ + int i, r; + struct mov_t* mov; + struct mov_track_t* track; + + mov = &reader->mov; + r = mov_reader_root(mov); + if (0 != r) { /*return r;*/ } // ignore file read error(for streaming file) + + for (i = 0; i < mov->track_count; i++) + { + track = mov->tracks + i; + mov_index_build(track); + //track->sample_offset = 0; // reset + + // fragment mp4 + if (0 == track->mdhd.duration && track->sample_count > 0) + track->mdhd.duration = track->samples[track->sample_count - 1].dts - track->samples[0].dts; + if (0 == track->tkhd.duration) + track->tkhd.duration = track->mdhd.duration * mov->mvhd.timescale / track->mdhd.timescale; + if (track->tkhd.duration > mov->mvhd.duration) + mov->mvhd.duration = track->tkhd.duration; // maximum track duration + } + + return 0; +} + +struct mov_reader_t* mov_reader_create(const struct mov_buffer_t* buffer, void* param) +{ + struct mov_reader_t* reader; + reader = (struct mov_reader_t*)calloc(1, sizeof(*reader)); + if (NULL == reader) + return NULL; + +#if defined(MOV_READER_FMP4_FAST) + reader->flags |= MOV_READER_FLAG_FMP4_FAST; +#endif + + // ISO/IEC 14496-12:2012(E) 4.3.1 Definition (p17) + // Files with no file-type box should be read as if they contained an FTYP box + // with Major_brand='mp41', minor_version=0, and the single compatible brand 'mp41'. + reader->mov.ftyp.major_brand = MOV_BRAND_MP41; + reader->mov.ftyp.minor_version = 0; + reader->mov.ftyp.brands_count = 0; + reader->mov.header = 0; + + reader->mov.io.param = param; + memcpy(&reader->mov.io.io, buffer, sizeof(reader->mov.io.io)); + if (0 != mov_reader_init(reader)) + { + mov_reader_destroy(reader); + return NULL; + } + return reader; +} + +void mov_reader_destroy(struct mov_reader_t* reader) +{ + int i; + for (i = 0; i < reader->mov.track_count; i++) + mov_free_track(reader->mov.tracks + i); + if (reader->mov.tracks) + free(reader->mov.tracks); + free(reader); +} + +static struct mov_track_t* mov_reader_next(struct mov_reader_t* reader) +{ + int i; + int64_t dts, best_dts = 0; + struct mov_track_t* track = NULL; + struct mov_track_t* track2; + + for (i = 0; i < reader->mov.track_count; i++) + { + track2 = &reader->mov.tracks[i]; + assert(track2->sample_offset <= track2->sample_count); + if (track2->sample_offset >= track2->sample_count) + continue; + + dts = track2->samples[track2->sample_offset].dts * 1000 / track2->mdhd.timescale; + //if (NULL == track || dts < best_dts) + //if (NULL == track || track->samples[track->sample_offset].offset > track2->samples[track2->sample_offset].offset) + if (NULL == track || (dts < best_dts && best_dts - dts > AV_TRACK_TIMEBASE) || track2->samples[track2->sample_offset].offset < track->samples[track->sample_offset].offset) + { + track = track2; + best_dts = dts; + } + } + + return track; +} + +int mov_reader_read2(struct mov_reader_t* reader, mov_reader_onread2 onread, void* param) +{ + void* ptr; + struct mov_track_t* track; + struct mov_sample_t* sample; + +FMP4_NEXT_FRAGMENT: + track = mov_reader_next(reader); + if (NULL == track || 0 == track->mdhd.timescale) + { + if ((MOV_READER_FLAG_FMP4_FAST & reader->flags) && reader->have_read_mfra + && 0 == mov_fragment_read_next_moof(&reader->mov)) + { + goto FMP4_NEXT_FRAGMENT; + } + return 0; // EOF + } + + assert(track->sample_offset < track->sample_count); + sample = &track->samples[track->sample_offset]; + assert(sample->sample_description_index > 0); + ptr = onread(param, track->tkhd.track_ID, /*sample->sample_description_index-1,*/ sample->bytes, sample->pts * 1000 / track->mdhd.timescale, sample->dts * 1000 / track->mdhd.timescale, sample->flags); + if(!ptr) + return -ENOMEM; + + mov_buffer_seek(&reader->mov.io, sample->offset); + mov_buffer_read(&reader->mov.io, ptr, sample->bytes); + if (mov_buffer_error(&reader->mov.io)) + { + // TODO: user free buffer + return mov_buffer_error(&reader->mov.io); + } + + track->sample_offset++; //mark as read + return 1; +} + +static void* mov_reader_read_helper(void* param, uint32_t track, size_t bytes, int64_t pts, int64_t dts, int flags) +{ + struct mov_sample_t* sample; + sample = (struct mov_sample_t*)param; + if (sample->bytes < bytes) + return NULL; + + sample->pts = pts; + sample->dts = dts; + sample->flags = flags; + sample->bytes = (uint32_t)bytes; + sample->sample_description_index = track; + return sample->data; +} + +int mov_reader_read(struct mov_reader_t* reader, void* buffer, size_t bytes, mov_reader_onread onread, void* param) +{ + int r; + struct mov_sample_t sample; // temp + //memset(&sample, 0, sizeof(sample)); + sample.data = buffer; + sample.bytes = (uint32_t)bytes; + r = mov_reader_read2(reader, mov_reader_read_helper, &sample); + if (r <= 0) + return r; + + onread(param, sample.sample_description_index, buffer, sample.bytes, sample.pts, sample.dts, sample.flags); + return 1; +} + +int mov_reader_seek(struct mov_reader_t* reader, int64_t* timestamp) +{ + int i; + struct mov_track_t* track; + + if (reader->have_read_mfra && (MOV_READER_FLAG_FMP4_FAST & reader->flags) + && reader->mov.track_count > 0 && reader->mov.tracks[0].frag_count > 0) + return mov_fragment_seek(&reader->mov, timestamp); + + // seek video track(s) + for (i = 0; i < reader->mov.track_count; i++) + { + track = &reader->mov.tracks[i]; + if (MOV_VIDEO == track->handler_type && track->stbl.stss_count > 0) + { + if (0 != mov_stss_seek(track, timestamp)) + return -1; + } + } + + // seek other track(s) + for (i = 0; i < reader->mov.track_count; i++) + { + track = &reader->mov.tracks[i]; + if (MOV_VIDEO == track->handler_type && track->stbl.stss_count > 0) + continue; // seek done + + mov_sample_seek(track, *timestamp); + } + + return 0; +} + +int mov_reader_getinfo(struct mov_reader_t* reader, struct mov_reader_trackinfo_t *ontrack, void* param) +{ + int i; + uint32_t j; + struct mov_track_t* track; + struct mov_sample_entry_t* entry; + + for (i = 0; i < reader->mov.track_count; i++) + { + track = &reader->mov.tracks[i]; + for (j = 0; j < track->stsd.entry_count && j < 1 /* only the first */; j++) + { + entry = &track->stsd.entries[j]; + switch (track->handler_type) + { + case MOV_VIDEO: + if(ontrack->onvideo) ontrack->onvideo(param, track->tkhd.track_ID, entry->object_type_indication, entry->u.visual.width, entry->u.visual.height, entry->extra_data, entry->extra_data_size); + break; + + case MOV_AUDIO: + if (ontrack->onaudio) ontrack->onaudio(param, track->tkhd.track_ID, entry->object_type_indication, entry->u.audio.channelcount, entry->u.audio.samplesize, entry->u.audio.samplerate >> 16, entry->extra_data, entry->extra_data_size); + break; + + case MOV_SUBT: + case MOV_TEXT: + case MOV_SBTL: + if (ontrack->onsubtitle) ontrack->onsubtitle(param, track->tkhd.track_ID, entry->object_type_indication, entry->extra_data, entry->extra_data_size); + break; + + default: + break; + } + } + } + return 0; +} + +uint64_t mov_reader_getduration(struct mov_reader_t* reader) +{ + return 0 != reader->mov.mvhd.timescale ? reader->mov.mvhd.duration * 1000 / reader->mov.mvhd.timescale : 0; +} + +#define DIFF(a, b) ((a) > (b) ? ((a) - (b)) : ((b) - (a))) + +static int mov_stss_seek(struct mov_track_t* track, int64_t *timestamp) +{ + int64_t clock; + size_t start, end, mid; + size_t idx, prev, next; + struct mov_sample_t* sample; + + idx = mid = start = 0; + end = track->stbl.stss_count; + assert(track->stbl.stss_count > 0); + clock = *timestamp * track->mdhd.timescale / 1000; // mvhd timescale + + while (start < end) + { + mid = (start + end) / 2; + idx = track->stbl.stss[mid]; + + if (idx < 1 || idx > track->sample_count) + { + // start from 1 + assert(0); + return -1; + } + idx -= 1; + sample = &track->samples[idx]; + + if (sample->dts > clock) + end = mid; + else if (sample->dts < clock) + start = mid + 1; + else + break; + } + + prev = track->stbl.stss[mid > 0 ? mid - 1 : mid] - 1; + next = track->stbl.stss[mid + 1 < track->stbl.stss_count ? mid + 1 : mid] - 1; + if (DIFF(track->samples[prev].dts, clock) < DIFF(track->samples[idx].dts, clock)) + idx = prev; + if (DIFF(track->samples[next].dts, clock) < DIFF(track->samples[idx].dts, clock)) + idx = next; + + *timestamp = track->samples[idx].dts * 1000 / track->mdhd.timescale; + track->sample_offset = idx; + return 0; +} + +static int mov_sample_seek(struct mov_track_t* track, int64_t timestamp) +{ + size_t prev, next; + size_t start, end, mid; + struct mov_sample_t* sample; + + if (track->sample_count < 1) + return -1; + + sample = NULL; + mid = start = 0; + end = track->sample_count; + timestamp = timestamp * track->mdhd.timescale / 1000; // mvhd timecale + + while (start < end) + { + mid = (start + end) / 2; + sample = track->samples + mid; + + if (sample->dts > timestamp) + end = mid; + else if (sample->dts < timestamp) + start = mid + 1; + else + break; + } + + prev = mid > 0 ? mid - 1 : mid; + next = mid + 1 < track->sample_count ? mid + 1 : mid; + if (DIFF(track->samples[prev].dts, timestamp) < DIFF(track->samples[mid].dts, timestamp)) + mid = prev; + if (DIFF(track->samples[next].dts, timestamp) < DIFF(track->samples[mid].dts, timestamp)) + mid = next; + + track->sample_offset = mid; + return 0; +} diff --git a/MediaServer/libmov/source/mov-sidx.c b/MediaServer/libmov/source/mov-sidx.c new file mode 100644 index 0000000..029115f --- /dev/null +++ b/MediaServer/libmov/source/mov-sidx.c @@ -0,0 +1,73 @@ +#include "mov-internal.h" +#include + +// 8.16.3 Segment Index Box (p119) +int mov_read_sidx(struct mov_t* mov, const struct mov_box_t* box) +{ + unsigned int version; + unsigned int i, reference_count; + + version = mov_buffer_r8(&mov->io); /* version */ + mov_buffer_r24(&mov->io); /* flags */ + mov_buffer_r32(&mov->io); /* reference_ID */ + mov_buffer_r32(&mov->io); /* timescale */ + + if (0 == version) + { + mov_buffer_r32(&mov->io); /* earliest_presentation_time */ + mov_buffer_r32(&mov->io); /* first_offset */ + } + else + { + mov_buffer_r64(&mov->io); /* earliest_presentation_time */ + mov_buffer_r64(&mov->io); /* first_offset */ + } + + mov_buffer_r16(&mov->io); /* reserved */ + reference_count = mov_buffer_r16(&mov->io); /* reference_count */ + for (i = 0; i < reference_count; i++) + { + mov_buffer_r32(&mov->io); /* reference_type & referenced_size */ + mov_buffer_r32(&mov->io); /* subsegment_duration */ + mov_buffer_r32(&mov->io); /* starts_with_SAP & SAP_type & SAP_delta_time */ + } + + (void)box; + return mov_buffer_error(&mov->io); +} + +size_t mov_write_sidx(const struct mov_t* mov, uint64_t offset) +{ + uint32_t duration; + uint64_t earliest_presentation_time; + const struct mov_track_t* track = mov->track; + + if (track->sample_count > 0) + { + earliest_presentation_time = track->samples[0].pts; + duration = (uint32_t)(track->samples[track->sample_count - 1].dts - track->samples[0].dts) + (uint32_t)track->turn_last_duration; + } + else + { + duration = 0; + earliest_presentation_time = 0; + } + + mov_buffer_w32(&mov->io, 52); /* size */ + mov_buffer_write(&mov->io, "sidx", 4); + mov_buffer_w8(&mov->io, 1); /* version */ + mov_buffer_w24(&mov->io, 0); /* flags */ + + mov_buffer_w32(&mov->io, track->tkhd.track_ID); /* reference_ID */ + mov_buffer_w32(&mov->io, track->mdhd.timescale); /* timescale */ + mov_buffer_w64(&mov->io, earliest_presentation_time); /* earliest_presentation_time */ + mov_buffer_w64(&mov->io, offset); /* first_offset */ + mov_buffer_w16(&mov->io, 0); /* reserved */ + mov_buffer_w16(&mov->io, 1); /* reference_count */ + + mov_buffer_w32(&mov->io, 0); /* reference_type & referenced_size */ + mov_buffer_w32(&mov->io, duration); /* subsegment_duration */ + mov_buffer_w32(&mov->io, (1U/*starts_with_SAP*/ << 31) | (1 /*SAP_type*/ << 28) | 0 /*SAP_delta_time*/); + + return 52; +} diff --git a/MediaServer/libmov/source/mov-stco.c b/MediaServer/libmov/source/mov-stco.c new file mode 100644 index 0000000..dbec696 --- /dev/null +++ b/MediaServer/libmov/source/mov-stco.c @@ -0,0 +1,174 @@ +#include "mov-internal.h" +#include +#include +#include + +// 8.7.5 Chunk Offset Box (p58) +/* +aligned(8) class ChunkOffsetBox extends FullBox('stco', version = 0, 0) { + unsigned int(32) entry_count; + for (i=1; i <= entry_count; i++) { + unsigned int(32) chunk_offset; + } +} + +aligned(8) class ChunkLargeOffsetBox extends FullBox('co64', version = 0, 0) { + unsigned int(32) entry_count; + for (i=1; i <= entry_count; i++) { + unsigned int(64) chunk_offset; + } +} +*/ + +int mov_read_stco(struct mov_t* mov, const struct mov_box_t* box) +{ + uint32_t i, entry_count; + struct mov_stbl_t* stbl = &mov->track->stbl; + + mov_buffer_r8(&mov->io); /* version */ + mov_buffer_r24(&mov->io); /* flags */ + entry_count = mov_buffer_r32(&mov->io); + + assert(0 == stbl->stco_count && NULL == stbl->stco); + if (stbl->stco_count < entry_count) + { + void* p = realloc(stbl->stco, sizeof(stbl->stco[0]) * entry_count); + if (NULL == p) return -ENOMEM; + stbl->stco = p; + } + stbl->stco_count = entry_count; + + if (MOV_TAG('s', 't', 'c', 'o') == box->type) + { + for (i = 0; i < entry_count; i++) + stbl->stco[i] = mov_buffer_r32(&mov->io); // chunk_offset + } + else if (MOV_TAG('c', 'o', '6', '4') == box->type) + { + for (i = 0; i < entry_count; i++) + stbl->stco[i] = mov_buffer_r64(&mov->io); // chunk_offset + } + else + { + i = 0; + assert(0); + } + + stbl->stco_count = i; + return mov_buffer_error(&mov->io); +} + +size_t mov_write_stco(const struct mov_t* mov, uint32_t count) +{ + int co64; + uint32_t size, i; + const struct mov_sample_t* sample; + const struct mov_track_t* track = mov->track; + + sample = track->sample_count > 0 ? &track->samples[track->sample_count - 1] : NULL; + co64 = (sample && sample->offset + track->offset > UINT32_MAX) ? 1 : 0; + size = 12/* full box */ + 4/* entry count */ + count * (co64 ? 8 : 4); + + mov_buffer_w32(&mov->io, size); /* size */ + mov_buffer_write(&mov->io, co64 ? "co64" : "stco", 4); + mov_buffer_w32(&mov->io, 0); /* version & flags */ + mov_buffer_w32(&mov->io, count); /* entry count */ + + for (i = 0; i < track->sample_count; i++) + { + sample = track->samples + i; + if(0 == sample->first_chunk) + continue; + + if(0 == co64) + mov_buffer_w32(&mov->io, (uint32_t)(sample->offset + track->offset)); + else + mov_buffer_w64(&mov->io, sample->offset + track->offset); + } + + return size; +} + +size_t mov_stco_size(const struct mov_track_t* track, uint64_t offset) +{ + size_t i, j; + uint64_t co64; + const struct mov_sample_t* sample; + + if (track->sample_count < 1) + return 0; + + sample = &track->samples[track->sample_count - 1]; + co64 = sample->offset + track->offset; + if (co64 > UINT32_MAX || co64 + offset <= UINT32_MAX) + return 0; + + for (i = 0, j = 0; i < track->sample_count; i++) + { + sample = track->samples + i; + if (0 != sample->first_chunk) + j++; + } + + return j * 4; +} + +uint32_t mov_build_stco(struct mov_track_t* track) +{ + size_t i; + size_t bytes = 0; + uint32_t count = 0; + struct mov_sample_t* sample = NULL; + + assert(track->stsd.entry_count > 0); + for (i = 0; i < track->sample_count; i++) + { + if (NULL != sample + && sample->offset + bytes == track->samples[i].offset + && sample->sample_description_index == track->samples[i].sample_description_index) + { + track->samples[i].first_chunk = 0; // mark invalid value + bytes += track->samples[i].bytes; + ++sample->samples_per_chunk; + } + else + { + sample = &track->samples[i]; + sample->first_chunk = ++count; // chunk start from 1 + sample->samples_per_chunk = 1; + bytes = sample->bytes; + } + } + + return count; +} + +void mov_apply_stco(struct mov_track_t* track) +{ + uint32_t i, j, k; + uint64_t n, chunk_offset; + struct mov_stbl_t* stbl = &track->stbl; + + // sample offset + assert(stbl->stsc_count > 0 && stbl->stco_count > 0); + stbl->stsc[stbl->stsc_count].first_chunk = stbl->stco_count + 1; // fill stco count + for (i = 0, n = 0; i < stbl->stsc_count; i++) + { + assert(stbl->stsc[i].first_chunk <= stbl->stco_count); + for (j = stbl->stsc[i].first_chunk; j < stbl->stsc[i + 1].first_chunk; j++) + { + chunk_offset = stbl->stco[j - 1]; // chunk start from 1 + for (k = 0; k < stbl->stsc[i].samples_per_chunk; k++, n++) + { + track->samples[n].sample_description_index = stbl->stsc[i].sample_description_index; + track->samples[n].offset = chunk_offset; + track->samples[n].data = NULL; + chunk_offset += track->samples[n].bytes; + assert(track->samples[n].bytes > 0); + assert(0 == n || track->samples[n - 1].offset + track->samples[n - 1].bytes <= track->samples[n].offset); + } + } + } + + assert(n == track->sample_count); +} diff --git a/MediaServer/libmov/source/mov-stsc.c b/MediaServer/libmov/source/mov-stsc.c new file mode 100644 index 0000000..3601c45 --- /dev/null +++ b/MediaServer/libmov/source/mov-stsc.c @@ -0,0 +1,86 @@ +#include "mov-internal.h" +#include +#include +#include + +// 8.7.4 Sample To Chunk Box (p57) +/* +aligned(8) class SampleToChunkBox extends FullBox('stsc', version = 0, 0) { + unsigned int(32) entry_count; + for (i=1; i <= entry_count; i++) { + unsigned int(32) first_chunk; + unsigned int(32) samples_per_chunk; + unsigned int(32) sample_description_index; + } +} +*/ +int mov_read_stsc(struct mov_t* mov, const struct mov_box_t* box) +{ + uint32_t i, entry_count; + struct mov_stbl_t* stbl = &mov->track->stbl; + + mov_buffer_r8(&mov->io); /* version */ + mov_buffer_r24(&mov->io); /* flags */ + entry_count = mov_buffer_r32(&mov->io); + + assert(0 == stbl->stsc_count && NULL == stbl->stsc); // duplicated STSC atom + if (stbl->stsc_count < entry_count) + { + void* p = realloc(stbl->stsc, sizeof(struct mov_stsc_t) * (entry_count + 1/*stco count*/)); + if (NULL == p) return -ENOMEM; + stbl->stsc = (struct mov_stsc_t*)p; + } + stbl->stsc_count = entry_count; + + for (i = 0; i < entry_count; i++) + { + stbl->stsc[i].first_chunk = mov_buffer_r32(&mov->io); + stbl->stsc[i].samples_per_chunk = mov_buffer_r32(&mov->io); + stbl->stsc[i].sample_description_index = mov_buffer_r32(&mov->io); + } + + (void)box; + return mov_buffer_error(&mov->io); +} + +size_t mov_write_stsc(const struct mov_t* mov) +{ + uint64_t offset; + uint64_t offset2; + uint32_t size, i, entry; + const struct mov_sample_t* chunk = NULL; + const struct mov_sample_t* sample = NULL; + const struct mov_track_t* track = mov->track; + + size = 12/* full box */ + 4/* entry count */; + + offset = mov_buffer_tell(&mov->io); + mov_buffer_w32(&mov->io, 0); /* size */ + mov_buffer_write(&mov->io, "stsc", 4); + mov_buffer_w32(&mov->io, 0); /* version & flags */ + mov_buffer_w32(&mov->io, 0); /* entry count */ + + for (i = 0, entry = 0; i < track->sample_count; i++) + { + sample = &track->samples[i]; + if (0 == sample->first_chunk || + (chunk && chunk->samples_per_chunk == sample->samples_per_chunk + && chunk->sample_description_index == sample->sample_description_index)) + continue; + + ++entry; + chunk = sample; + mov_buffer_w32(&mov->io, sample->first_chunk); + mov_buffer_w32(&mov->io, sample->samples_per_chunk); + mov_buffer_w32(&mov->io, sample->sample_description_index); + } + + size += entry * 12/* entry size*/; + offset2 = mov_buffer_tell(&mov->io); + mov_buffer_seek(&mov->io, offset); + mov_buffer_w32(&mov->io, size); /* size */ + mov_buffer_seek(&mov->io, offset + 12); + mov_buffer_w32(&mov->io, entry); /* entry count */ + mov_buffer_seek(&mov->io, offset2); + return size; +} diff --git a/MediaServer/libmov/source/mov-stsd.c b/MediaServer/libmov/source/mov-stsd.c new file mode 100644 index 0000000..1ed561b --- /dev/null +++ b/MediaServer/libmov/source/mov-stsd.c @@ -0,0 +1,576 @@ +#include "mov-internal.h" +#include +#include +#include +#include + +// stsd: Sample Description Box + +int mp4_read_extra(struct mov_t* mov, const struct mov_box_t* box) +{ + int r; + uint64_t p1, p2; + p1 = mov_buffer_tell(&mov->io); + r = mov_reader_box(mov, box); + p2 = mov_buffer_tell(&mov->io); + mov_buffer_skip(&mov->io, box->size - (p2 - p1)); + return r; +} + +/* +aligned(8) abstract class SampleEntry (unsigned int(32) format) + extends Box(format){ + const unsigned int(8)[6] reserved = 0; + unsigned int(16) data_reference_index; +} +*/ +static int mov_read_sample_entry(struct mov_t* mov, struct mov_box_t* box, uint16_t* data_reference_index) +{ + box->size = mov_buffer_r32(&mov->io); + box->type = mov_buffer_r32(&mov->io); + mov_buffer_skip(&mov->io, 6); // const unsigned int(8)[6] reserved = 0; + *data_reference_index = (uint16_t)mov_buffer_r16(&mov->io); // ref [dref] + return 0; +} + +/* +class AudioSampleEntry(codingname) extends SampleEntry (codingname){ + const unsigned int(32)[2] reserved = 0; + template unsigned int(16) channelcount = 2; + template unsigned int(16) samplesize = 16; + unsigned int(16) pre_defined = 0; + const unsigned int(16) reserved = 0 ; + template unsigned int(32) samplerate = { default samplerate of media}<<16; +} +*/ +static int mov_read_audio(struct mov_t* mov, struct mov_sample_entry_t* entry) +{ + uint16_t qtver; + struct mov_box_t box; + mov_read_sample_entry(mov, &box, &entry->data_reference_index); + entry->object_type_indication = mov_tag_to_object(box.type); + entry->stream_type = MP4_STREAM_AUDIO; + mov->track->tag = box.type; + +#if 0 + // const unsigned int(32)[2] reserved = 0; + mov_buffer_skip(&mov->io, 8); +#else + qtver = mov_buffer_r16(&mov->io); /* version */ + mov_buffer_r16(&mov->io); /* revision level */ + mov_buffer_r32(&mov->io); /* vendor */ +#endif + + entry->u.audio.channelcount = (uint16_t)mov_buffer_r16(&mov->io); + entry->u.audio.samplesize = (uint16_t)mov_buffer_r16(&mov->io); + +#if 0 + // unsigned int(16) pre_defined = 0; + // const unsigned int(16) reserved = 0 ; + mov_buffer_skip(&mov->io, 4); +#else + mov_buffer_r16(&mov->io); /* audio cid */ + mov_buffer_r16(&mov->io); /* packet size = 0 */ +#endif + + entry->u.audio.samplerate = mov_buffer_r32(&mov->io); // { default samplerate of media}<<16; + + // audio extra(avc1: ISO/IEC 14496-14:2003(E)) + box.size -= 36; + + // https://developer.apple.com/library/archive/documentation/QuickTime/QTFF/QTFFChap3/qtff3.html#//apple_ref/doc/uid/TP40000939-CH205-124774 + if (1 == qtver && box.size >= 16) + { + // Sound Sample Description (Version 1) + mov_buffer_r32(&mov->io); // Samples per packet + mov_buffer_r32(&mov->io); // Bytes per packet + mov_buffer_r32(&mov->io); // Bytes per frame + mov_buffer_r32(&mov->io); // Bytes per sample + box.size -= 16; + } + else if (2 == qtver && box.size >= 36) + { + // Sound Sample Description (Version 2) + mov_buffer_r32(&mov->io); // sizeOfStructOnly + mov_buffer_r64(&mov->io); // audioSampleRate + mov_buffer_r32(&mov->io); // numAudioChannels + mov_buffer_r32(&mov->io); // always7F000000 + mov_buffer_r32(&mov->io); // constBitsPerChannel + mov_buffer_r32(&mov->io); // formatSpecificFlags + mov_buffer_r32(&mov->io); // constBytesPerAudioPacket + mov_buffer_r32(&mov->io); // constLPCMFramesPerAudioPacket + box.size -= 36; + } + + return mp4_read_extra(mov, &box); +} + +/* +class VisualSampleEntry(codingname) extends SampleEntry (codingname){ + unsigned int(16) pre_defined = 0; + const unsigned int(16) reserved = 0; + unsigned int(32)[3] pre_defined = 0; + unsigned int(16) width; + unsigned int(16) height; + template unsigned int(32) horizresolution = 0x00480000; // 72 dpi + template unsigned int(32) vertresolution = 0x00480000; // 72 dpi + const unsigned int(32) reserved = 0; + template unsigned int(16) frame_count = 1; + string[32] compressorname; + template unsigned int(16) depth = 0x0018; + int(16) pre_defined = -1; + // other boxes from derived specifications + CleanApertureBox clap; // optional + PixelAspectRatioBox pasp; // optional +} +class AVCSampleEntry() extends VisualSampleEntry ('avc1'){ + AVCConfigurationBox config; + MPEG4BitRateBox (); // optional + MPEG4ExtensionDescriptorsBox (); // optional +} +class AVC2SampleEntry() extends VisualSampleEntry ('avc2'){ + AVCConfigurationBox avcconfig; + MPEG4BitRateBox bitrate; // optional + MPEG4ExtensionDescriptorsBox descr; // optional + extra_boxes boxes; // optional +} +*/ +static int mov_read_video(struct mov_t* mov, struct mov_sample_entry_t* entry) +{ + struct mov_box_t box; + mov_read_sample_entry(mov, &box, &entry->data_reference_index); + entry->object_type_indication = mov_tag_to_object(box.type); + entry->stream_type = MP4_STREAM_VISUAL; + mov->track->tag = box.type; +#if 1 + //unsigned int(16) pre_defined = 0; + //const unsigned int(16) reserved = 0; + //unsigned int(32)[3] pre_defined = 0; + mov_buffer_skip(&mov->io, 16); +#else + mov_buffer_r16(&mov->io); /* version */ + mov_buffer_r16(&mov->io); /* revision level */ + mov_buffer_r32(&mov->io); /* vendor */ + mov_buffer_r32(&mov->io); /* temporal quality */ + mov_buffer_r32(&mov->io); /* spatial quality */ +#endif + entry->u.visual.width = (uint16_t)mov_buffer_r16(&mov->io); + entry->u.visual.height = (uint16_t)mov_buffer_r16(&mov->io); + entry->u.visual.horizresolution = mov_buffer_r32(&mov->io); // 0x00480000 - 72 dpi + entry->u.visual.vertresolution = mov_buffer_r32(&mov->io); // 0x00480000 - 72 dpi + // const unsigned int(32) reserved = 0; + mov_buffer_r32(&mov->io); /* data size, always 0 */ + entry->u.visual.frame_count = (uint16_t)mov_buffer_r16(&mov->io); + + //string[32] compressorname; + //uint32_t len = mov_buffer_r8(&mov->io); + //mov_buffer_skip(&mov->io, len); + mov_buffer_skip(&mov->io, 32); + + entry->u.visual.depth = (uint16_t)mov_buffer_r16(&mov->io); + // int(16) pre_defined = -1; + mov_buffer_skip(&mov->io, 2); + + // video extra(avc1: ISO/IEC 14496-15:2010(E)) + box.size -= 86; + return mp4_read_extra(mov, &box); +} + +/* +class PixelAspectRatioBox extends Box(pasp){ + unsigned int(32) hSpacing; + unsigned int(32) vSpacing; +} +*/ +int mov_read_pasp(struct mov_t* mov, const struct mov_box_t* box) +{ + mov_buffer_r32(&mov->io); + mov_buffer_r32(&mov->io); + + (void)box; + return 0; +} + +static int mov_read_hint_sample_entry(struct mov_t* mov, struct mov_sample_entry_t* entry) +{ + struct mov_box_t box; + mov_read_sample_entry(mov, &box, &entry->data_reference_index); + mov_buffer_skip(&mov->io, box.size - 16); + entry->object_type_indication = mov_tag_to_object(box.type); + entry->stream_type = MP4_STREAM_VISUAL; + mov->track->tag = box.type; + return mov_buffer_error(&mov->io); +} + +static int mov_read_meta_sample_entry(struct mov_t* mov, struct mov_sample_entry_t* entry) +{ + struct mov_box_t box; + mov_read_sample_entry(mov, &box, &entry->data_reference_index); + mov_buffer_skip(&mov->io, box.size - 16); + entry->object_type_indication = mov_tag_to_object(box.type); + entry->stream_type = MP4_STREAM_VISUAL; + mov->track->tag = box.type; + return mov_buffer_error(&mov->io); +} + +// ISO/IEC 14496-12:2015(E) 12.5 Text media (p184) +/* +class PlainTextSampleEntry(codingname) extends SampleEntry (codingname) { +} +class SimpleTextSampleEntry(codingname) extends PlainTextSampleEntry ('stxt') { + string content_encoding; // optional + string mime_format; + BitRateBox (); // optional + TextConfigBox (); // optional +} +*/ +static int mov_read_text_sample_entry(struct mov_t* mov, struct mov_sample_entry_t* entry) +{ + struct mov_box_t box; + mov_read_sample_entry(mov, &box, &entry->data_reference_index); + if (MOV_TEXT == box.type) + { + // https://developer.apple.com/documentation/quicktime-file-format/text_sample_description + //mov_buffer_r32(&mov->io); /* display flags */ + //mov_buffer_r32(&mov->io); /* text justification */ + //mov_buffer_r16(&mov->io); /* background color: 48-bit RGB color */ + //mov_buffer_r16(&mov->io); + //mov_buffer_r16(&mov->io); + //mov_buffer_r64(&mov->io); /* default text box (top, left, bottom, right) */ + //mov_buffer_r64(&mov->io); /* reserved */ + //mov_buffer_r16(&mov->io); /* font number */ + //mov_buffer_r16(&mov->io); /* font face */ + //mov_buffer_r8(&mov->io); /* reserved */ + //mov_buffer_r16(&mov->io); /* reserved */ + //mov_buffer_r16(&mov->io); /* foreground color: 48-bit RGB color */ + //mov_buffer_r16(&mov->io); + //mov_buffer_r16(&mov->io); + ////mov_buffer_r16(&mov->io); /* text name */ + mov_buffer_skip(&mov->io, box.size - 16); + } + else + { + mov_buffer_skip(&mov->io, box.size - 16); + } + + entry->object_type_indication = mov_tag_to_object(box.type); + entry->stream_type = MP4_STREAM_VISUAL; + mov->track->tag = box.type; + return mov_buffer_error(&mov->io); +} + +// ISO/IEC 14496-12:2015(E) 12.6 Subtitle media (p185) +/* +class SubtitleSampleEntry(codingname) extends SampleEntry (codingname) { +} +class XMLSubtitleSampleEntry() extends SubtitleSampleEntry('stpp') { + string namespace; + string schema_location; // optional + string auxiliary_mime_types; + // optional, required if auxiliary resources are present + BitRateBox (); // optional +} +class TextSubtitleSampleEntry() extends SubtitleSampleEntry('sbtt') { + string content_encoding; // optional + string mime_format; + BitRateBox (); // optional + TextConfigBox (); // optional +} +class TextSampleEntry() extends SampleEntry('tx3g') { + unsigned int(32) displayFlags; + signed int(8) horizontal-justification; + signed int(8) vertical-justification; + unsigned int(8) background-color-rgba[4]; + BoxRecord default-text-box; + StyleRecord default-style; + FontTableBox font-table; + DisparityBox default-disparity; +} +*/ +static int mov_read_subtitle_sample_entry(struct mov_t* mov, struct mov_sample_entry_t* entry) +{ + struct mov_box_t box; + mov_read_sample_entry(mov, &box, &entry->data_reference_index); + box.size -= 16; + if (box.type == MOV_TAG('t', 'x', '3', 'g')) + { + mov_read_tx3g(mov, &box); + } + else + { + mov_buffer_skip(&mov->io, box.size - 16); + } + + entry->object_type_indication = MOV_OBJECT_TEXT; + entry->stream_type = MP4_STREAM_VISUAL; + mov->track->tag = box.type; + return mov_buffer_error(&mov->io); +} + +int mov_read_stsd(struct mov_t* mov, const struct mov_box_t* box) +{ + uint32_t i, entry_count; + struct mov_track_t* track = mov->track; + + mov_buffer_r8(&mov->io); + mov_buffer_r24(&mov->io); + entry_count = mov_buffer_r32(&mov->io); + + if (track->stsd.entry_count < entry_count) + { + void* p = realloc(track->stsd.entries, sizeof(track->stsd.entries[0]) * entry_count); + if (NULL == p) return -ENOMEM; + track->stsd.entries = (struct mov_sample_entry_t*)p; + } + + track->stsd.entry_count = entry_count; + for (i = 0; i < entry_count; i++) + { + track->stsd.current = &track->stsd.entries[i]; + memset(track->stsd.current, 0, sizeof(*track->stsd.current)); + if (MOV_AUDIO == track->handler_type) + { + mov_read_audio(mov, &track->stsd.entries[i]); + } + else if (MOV_VIDEO == track->handler_type) + { + mov_read_video(mov, &track->stsd.entries[i]); + } + else if (MOV_HINT == track->handler_type) + { + mov_read_hint_sample_entry(mov, &track->stsd.entries[i]); + } + else if (MOV_META == track->handler_type) + { + mov_read_meta_sample_entry(mov, &track->stsd.entries[i]); + } + else if (MOV_CLCP == track->handler_type) + { + mov_read_meta_sample_entry(mov, &track->stsd.entries[i]); + } + else if (MOV_TEXT == track->handler_type) + { + mov_read_text_sample_entry(mov, &track->stsd.entries[i]); + } + else if (MOV_SUBT == track->handler_type || MOV_SBTL == track->handler_type) + { + mov_read_subtitle_sample_entry(mov, &track->stsd.entries[i]); + } + else if (MOV_ALIS == track->handler_type) + { + mov_read_meta_sample_entry(mov, &track->stsd.entries[i]); + } + else + { + assert(0); // ignore + mov_read_meta_sample_entry(mov, &track->stsd.entries[i]); + } + } + + (void)box; + return mov_buffer_error(&mov->io); +} + +//static int mov_write_h264(const struct mov_t* mov) +//{ +// size_t size; +// uint64_t offset; +// const struct mov_track_t* track = mov->track; +// +// size = 8 /* Box */; +// +// offset = mov_buffer_tell(&mov->io); +// mov_buffer_w32(&mov->io, 0); /* size */ +// mov_buffer_w32(&mov->io, MOV_TAG('a', 'v', 'c', 'C')); +// +// mov_write_size(mov, offset, size); /* update size */ +// return size; +//} + +static size_t mov_write_btrt(const struct mov_t* mov, const struct mov_sample_entry_t* entry) +{ + mov_buffer_w32(&mov->io, 20); /* size */ + mov_buffer_write(&mov->io, "btrt", 4); + mov_buffer_w32(&mov->io, entry->u.bitrate.bufferSizeDB); + mov_buffer_w32(&mov->io, 0x00000014); + mov_buffer_w32(&mov->io, 0x00000014); + //mov_buffer_w32(&mov->io, entry->u.bitrate.maxBitrate); + //mov_buffer_w32(&mov->io, entry->u.bitrate.avgBitrate); + return 20; +} + +static size_t mov_write_video(const struct mov_t* mov, const struct mov_sample_entry_t* entry) +{ + size_t size; + uint64_t offset; + char compressorname[32]; + memset(compressorname, 0, sizeof(compressorname)); + assert(1 == entry->data_reference_index); + + size = 8 /* Box */ + 8 /* SampleEntry */ + 70 /* VisualSampleEntry */; + + offset = mov_buffer_tell(&mov->io); + mov_buffer_w32(&mov->io, 0); /* size */ + mov_buffer_w32(&mov->io, mov->track->tag); // "h264" + + mov_buffer_w32(&mov->io, 0); /* Reserved */ + mov_buffer_w16(&mov->io, 0); /* Reserved */ + mov_buffer_w16(&mov->io, entry->data_reference_index); /* Data-reference index */ + + mov_buffer_w16(&mov->io, 0); /* Reserved / Codec stream version */ + mov_buffer_w16(&mov->io, 0); /* Reserved / Codec stream revision (=0) */ + mov_buffer_w32(&mov->io, 0); /* Reserved */ + mov_buffer_w32(&mov->io, 0); /* Reserved */ + mov_buffer_w32(&mov->io, 0); /* Reserved */ + + mov_buffer_w16(&mov->io, entry->u.visual.width); /* Video width */ + mov_buffer_w16(&mov->io, entry->u.visual.height); /* Video height */ + mov_buffer_w32(&mov->io, 0x00480000); /* Horizontal resolution 72dpi */ + mov_buffer_w32(&mov->io, 0x00480000); /* Vertical resolution 72dpi */ + mov_buffer_w32(&mov->io, 0); /* reserved / Data size (= 0) */ + mov_buffer_w16(&mov->io, 1); /* Frame count (= 1) */ + + // ISO 14496-15:2017 AVCC \012AVC Coding + // ISO 14496-15:2017 HVCC \013HEVC Coding + //mov_buffer_w8(&mov->io, 0 /*strlen(compressor_name)*/); /* compressorname */ + mov_buffer_write(&mov->io, compressorname, 32); // fill empty + + // ISO/IEC 14496-15:2017 4.5 Template field used (19) + // 0x18 - the video sequence is in color with no alpha + // 0x28 - the video sequence is in grayscale with no alpha + // 0x20 - the video sequence has alpha (gray or color) + mov_buffer_w16(&mov->io, 0x18); /* Reserved */ + mov_buffer_w16(&mov->io, 0xffff); /* Reserved */ + + if(MOV_OBJECT_H264 == entry->object_type_indication) + size += mov_write_avcc(mov); + else if (MOV_OBJECT_H265 == entry->object_type_indication) + size += mov_write_hvcc(mov); + else if (MOV_OBJECT_H266 == entry->object_type_indication) + size += mov_write_vvcc(mov); + else if (MOV_OBJECT_MP4V == entry->object_type_indication || MOV_OBJECT_JPEG == entry->object_type_indication || MOV_OBJECT_PNG == entry->object_type_indication || MOV_OBJECT_JPEG2000 == entry->object_type_indication) + size += mov_write_esds(mov); + else if (MOV_OBJECT_AV1 == entry->object_type_indication) + size += mov_write_av1c(mov); + else if (MOV_OBJECT_VP8 == entry->object_type_indication || MOV_OBJECT_VP9 == entry->object_type_indication) + size += mov_write_vpcc(mov); + + //size += mov_write_btrt(mov, entry); + mov_write_size(mov, offset, size); /* update size */ + return size; +} + +static size_t mov_write_audio(const struct mov_t* mov, const struct mov_sample_entry_t* entry) +{ + size_t size; + uint64_t offset; + + size = 8 /* Box */ + 8 /* SampleEntry */ + 20 /* AudioSampleEntry */; + + offset = mov_buffer_tell(&mov->io); + mov_buffer_w32(&mov->io, 0); /* size */ + mov_buffer_w32(&mov->io, mov->track->tag); // "mp4a" + + mov_buffer_w32(&mov->io, 0); /* Reserved */ + mov_buffer_w16(&mov->io, 0); /* Reserved */ + mov_buffer_w16(&mov->io, 1); /* Data-reference index */ + + /* SoundDescription */ + mov_buffer_w16(&mov->io, 0); /* Version */ + mov_buffer_w16(&mov->io, 0); /* Revision level */ + mov_buffer_w32(&mov->io, 0); /* Reserved */ + + mov_buffer_w16(&mov->io, entry->u.audio.channelcount); /* channelcount */ + mov_buffer_w16(&mov->io, entry->u.audio.samplesize); /* samplesize */ + + mov_buffer_w16(&mov->io, 0); /* pre_defined */ + mov_buffer_w16(&mov->io, 0); /* reserved / packet size (= 0) */ + + // https://www.opus-codec.org/docs/opus_in_isobmff.html + // 4.3 Definitions of Opus sample + // OpusSampleEntry: + // 1. The samplesize field shall be set to 16. + // 2. The samplerate field shall be set to 48000<<16. + mov_buffer_w32(&mov->io, entry->u.audio.samplerate); /* samplerate */ + + if(MOV_OBJECT_AAC == entry->object_type_indication || MOV_OBJECT_MP3 == entry->object_type_indication || MOV_OBJECT_MP1A == entry->object_type_indication) + size += mov_write_esds(mov); + else if(MOV_OBJECT_OPUS == entry->object_type_indication) + size += mov_write_dops(mov); + + //size += mov_write_btrt(mov, entry); + mov_write_size(mov, offset, size); /* update size */ + return size; +} + +static int mov_write_subtitle(const struct mov_t* mov, const struct mov_sample_entry_t* entry) +{ + int size; + uint64_t offset; + + size = 8 /* Box */ + 8 /* SampleEntry */; + + offset = mov_buffer_tell(&mov->io); + mov_buffer_w32(&mov->io, 0); /* size */ + mov_buffer_w32(&mov->io, mov->track->tag); // "tx3g" + + mov_buffer_w32(&mov->io, 0); /* Reserved */ + mov_buffer_w16(&mov->io, 0); /* Reserved */ + mov_buffer_w16(&mov->io, entry->data_reference_index); /* Data-reference index */ + + if (MOV_TAG('t', 'x', '3', 'g') == mov->track->tag) + { + size += mov_write_tx3g(mov); + } + else if (entry->extra_data_size > 0) // unknown type + { + mov_buffer_write(&mov->io, entry->extra_data, entry->extra_data_size); + size += entry->extra_data_size; + + size += mov_write_btrt(mov, entry); + } + + mov_write_size(mov, offset, size); /* update size */ + return size; +} + +size_t mov_write_stsd(const struct mov_t* mov) +{ + uint32_t i; + size_t size; + uint64_t offset; + const struct mov_track_t* track = mov->track; + + size = 12 /* full box */ + 4 /* entry count */; + + offset = mov_buffer_tell(&mov->io); + mov_buffer_w32(&mov->io, 0); /* size */ + mov_buffer_write(&mov->io, "stsd", 4); + mov_buffer_w32(&mov->io, 0); /* version & flags */ + mov_buffer_w32(&mov->io, track->stsd.entry_count); /* entry count */ + + for (i = 0; i < track->stsd.entry_count; i++) + { + ((struct mov_track_t*)track)->stsd.current = &track->stsd.entries[i]; + + if (MOV_VIDEO == track->handler_type) + { + size += mov_write_video(mov, &track->stsd.entries[i]); + } + else if (MOV_AUDIO == track->handler_type) + { + size += mov_write_audio(mov, &track->stsd.entries[i]); + } + else if (MOV_SUBT == track->handler_type || MOV_TEXT == track->handler_type || MOV_SBTL == track->handler_type) + { + size += mov_write_subtitle(mov, &track->stsd.entries[i]); + } + else + { + assert(0); + } + } + + mov_write_size(mov, offset, size); /* update size */ + return size; +} diff --git a/MediaServer/libmov/source/mov-stss.c b/MediaServer/libmov/source/mov-stss.c new file mode 100644 index 0000000..279c1de --- /dev/null +++ b/MediaServer/libmov/source/mov-stss.c @@ -0,0 +1,79 @@ +#include "mov-internal.h" +#include +#include +#include + +// 8.6.2 Sync Sample Box (p50) +int mov_read_stss(struct mov_t* mov, const struct mov_box_t* box) +{ + uint32_t i, entry_count; + struct mov_stbl_t* stbl = &mov->track->stbl; + + mov_buffer_r8(&mov->io); /* version */ + mov_buffer_r24(&mov->io); /* flags */ + entry_count = mov_buffer_r32(&mov->io); + + assert(0 == stbl->stss_count && NULL == stbl->stss); + if (stbl->stss_count < entry_count) + { + void* p = realloc(stbl->stss, sizeof(stbl->stss[0]) * entry_count); + if (NULL == p) return -ENOMEM; + stbl->stss = p; + } + stbl->stss_count = entry_count; + + for (i = 0; i < entry_count; i++) + stbl->stss[i] = mov_buffer_r32(&mov->io); // uint32_t sample_number + + (void)box; + return mov_buffer_error(&mov->io); +} + +size_t mov_write_stss(const struct mov_t* mov) +{ + uint64_t offset; + uint64_t offset2; + uint32_t size, i, j; + const struct mov_sample_t* sample; + const struct mov_track_t* track = mov->track; + + size = 12/* full box */ + 4/* entry count */; + + offset = mov_buffer_tell(&mov->io); + mov_buffer_w32(&mov->io, 0); /* size */ + mov_buffer_write(&mov->io, "stss", 4); + mov_buffer_w32(&mov->io, 0); /* version & flags */ + mov_buffer_w32(&mov->io, 0); /* entry count */ + + for (i = 0, j = 0; i < track->sample_count; i++) + { + sample = &track->samples[i]; + if (sample->flags & MOV_AV_FLAG_KEYFREAME) + { + ++j; + mov_buffer_w32(&mov->io, i + 1); // start from 1 + } + } + + size += j * 4/* entry */; + offset2 = mov_buffer_tell(&mov->io); + mov_buffer_seek(&mov->io, offset); + mov_buffer_w32(&mov->io, size); /* size */ + mov_buffer_seek(&mov->io, offset + 12); + mov_buffer_w32(&mov->io, j); /* entry count */ + mov_buffer_seek(&mov->io, offset2); + return size; +} + +void mov_apply_stss(struct mov_track_t* track) +{ + size_t i, j; + struct mov_stbl_t* stbl = &track->stbl; + + for (i = 0; i < stbl->stss_count; i++) + { + j = stbl->stss[i]; // start from 1 + if (j > 0 && j <= track->sample_count) + track->samples[j - 1].flags |= MOV_AV_FLAG_KEYFREAME; + } +} diff --git a/MediaServer/libmov/source/mov-stsz.c b/MediaServer/libmov/source/mov-stsz.c new file mode 100644 index 0000000..fcf3b35 --- /dev/null +++ b/MediaServer/libmov/source/mov-stsz.c @@ -0,0 +1,131 @@ +#include "mov-internal.h" +#include +#include +#include +#include + +// 8.7.3.2 Sample Size Box (p57) +int mov_read_stsz(struct mov_t* mov, const struct mov_box_t* box) +{ + uint32_t i = 0, sample_size, sample_count; + struct mov_track_t* track = mov->track; + + mov_buffer_r8(&mov->io); /* version */ + mov_buffer_r24(&mov->io); /* flags */ + sample_size = mov_buffer_r32(&mov->io); + sample_count = mov_buffer_r32(&mov->io); + + assert(0 == track->sample_count && NULL == track->samples); // duplicated STSZ atom + if (track->sample_count < sample_count) + { + void* p = realloc(track->samples, sizeof(struct mov_sample_t) * (sample_count + 1)); + if (NULL == p) return -ENOMEM; + track->samples = (struct mov_sample_t*)p; + memset(track->samples, 0, sizeof(struct mov_sample_t) * (sample_count + 1)); + } + track->sample_count = sample_count; + + if (0 == sample_size) + { + for (i = 0; i < sample_count; i++) + track->samples[i].bytes = mov_buffer_r32(&mov->io); // uint32_t entry_size + } + else + { + for (i = 0; i < sample_count; i++) + track->samples[i].bytes = sample_size; + } + + (void)box; + return mov_buffer_error(&mov->io); +} + +// 8.7.3.3 Compact Sample Size Box (p57) +int mov_read_stz2(struct mov_t* mov, const struct mov_box_t* box) +{ + uint32_t i, v, field_size, sample_count; + struct mov_track_t* track = mov->track; + + mov_buffer_r8(&mov->io); /* version */ + mov_buffer_r24(&mov->io); /* flags */ + // unsigned int(24) reserved = 0; + mov_buffer_r24(&mov->io); /* reserved */ + field_size = mov_buffer_r8(&mov->io); + sample_count = mov_buffer_r32(&mov->io); + + assert(4 == field_size || 8 == field_size || 16 == field_size); + assert(0 == track->sample_count && NULL == track->samples); // duplicated STSZ atom + if (track->sample_count < sample_count) + { + void* p = realloc(track->samples, sizeof(struct mov_sample_t) * (sample_count + 1)); + if (NULL == p) return -ENOMEM; + track->samples = (struct mov_sample_t*)p; + memset(track->samples, 0, sizeof(struct mov_sample_t) * (sample_count + 1)); + } + track->sample_count = sample_count; + + if (4 == field_size) + { + for (i = 0; i < sample_count/2; i++) + { + v = mov_buffer_r8(&mov->io); + track->samples[i * 2].bytes = (v >> 4) & 0x0F; + track->samples[i * 2 + 1].bytes = v & 0x0F; + } + if (sample_count % 2) + { + v = mov_buffer_r8(&mov->io); + track->samples[i * 2].bytes = (v >> 4) & 0x0F; + } + } + else if (8 == field_size) + { + for (i = 0; i < sample_count; i++) + track->samples[i].bytes = mov_buffer_r8(&mov->io); + } + else if (16 == field_size) + { + for (i = 0; i < sample_count; i++) + track->samples[i].bytes = mov_buffer_r16(&mov->io); + } + else + { + i = 0; + assert(0); + } + + (void)box; + return mov_buffer_error(&mov->io); +} + +size_t mov_write_stsz(const struct mov_t* mov) +{ + uint32_t size, i; + const struct mov_track_t* track = mov->track; + + for(i = 1; i < track->sample_count; i++) + { + if(track->samples[i].bytes != track->samples[i-1].bytes) + break; + } + + size = 12/* full box */ + 8 + (i < track->sample_count ? 4 * track->sample_count : 0); + mov_buffer_w32(&mov->io, size); /* size */ + mov_buffer_write(&mov->io, "stsz", 4); + mov_buffer_w32(&mov->io, 0); /* version & flags */ + + if(i < track->sample_count) + { + mov_buffer_w32(&mov->io, 0); + mov_buffer_w32(&mov->io, track->sample_count); + for(i = 0; i < track->sample_count; i++) + mov_buffer_w32(&mov->io, track->samples[i].bytes); + } + else + { + mov_buffer_w32(&mov->io, track->sample_count < 1 ? 0 : track->samples[0].bytes); + mov_buffer_w32(&mov->io, track->sample_count); + } + + return size; +} diff --git a/MediaServer/libmov/source/mov-stts.c b/MediaServer/libmov/source/mov-stts.c new file mode 100644 index 0000000..08e73c2 --- /dev/null +++ b/MediaServer/libmov/source/mov-stts.c @@ -0,0 +1,243 @@ +#include "mov-internal.h" +#include +#include +#include + +// 8.6.1.2 Decoding Time to Sample Box (p47) +int mov_read_stts(struct mov_t* mov, const struct mov_box_t* box) +{ + uint32_t i, entry_count; + struct mov_stbl_t* stbl = &mov->track->stbl; + + mov_buffer_r8(&mov->io); /* version */ + mov_buffer_r24(&mov->io); /* flags */ + entry_count = mov_buffer_r32(&mov->io); + + assert(0 == stbl->stts_count && NULL == stbl->stts); // duplicated STTS atom + if (stbl->stts_count < entry_count) + { + void* p = realloc(stbl->stts, sizeof(struct mov_stts_t) * entry_count); + if (NULL == p) return -ENOMEM; + stbl->stts = (struct mov_stts_t*)p; + } + stbl->stts_count = entry_count; + + for (i = 0; i < entry_count; i++) + { + stbl->stts[i].sample_count = mov_buffer_r32(&mov->io); + stbl->stts[i].sample_delta = mov_buffer_r32(&mov->io); + } + + (void)box; + return mov_buffer_error(&mov->io); +} + +// 8.6.1.3 Composition Time to Sample Box (p47) +int mov_read_ctts(struct mov_t* mov, const struct mov_box_t* box) +{ + uint32_t i, entry_count; + struct mov_stbl_t* stbl = &mov->track->stbl; + + mov_buffer_r8(&mov->io); /* version */ + mov_buffer_r24(&mov->io); /* flags */ + entry_count = mov_buffer_r32(&mov->io); + + assert(0 == stbl->ctts_count && NULL == stbl->ctts); // duplicated CTTS atom + if (stbl->ctts_count < entry_count) + { + void* p = realloc(stbl->ctts, sizeof(struct mov_stts_t) * entry_count); + if (NULL == p) return -ENOMEM; + stbl->ctts = (struct mov_stts_t*)p; + } + stbl->ctts_count = entry_count; + + for (i = 0; i < entry_count; i++) + { + stbl->ctts[i].sample_count = mov_buffer_r32(&mov->io); + stbl->ctts[i].sample_delta = mov_buffer_r32(&mov->io); // parse at int32_t + } + + (void)box; + return mov_buffer_error(&mov->io); +} + +// 8.6.1.4 Composition to Decode Box (p53) +int mov_read_cslg(struct mov_t* mov, const struct mov_box_t* box) +{ + uint8_t version; +// struct mov_stbl_t* stbl = &mov->track->stbl; + + version = (uint8_t)mov_buffer_r8(&mov->io); /* version */ + mov_buffer_r24(&mov->io); /* flags */ + + if (0 == version) + { + mov_buffer_r32(&mov->io); /* compositionToDTSShift */ + mov_buffer_r32(&mov->io); /* leastDecodeToDisplayDelta */ + mov_buffer_r32(&mov->io); /* greatestDecodeToDisplayDelta */ + mov_buffer_r32(&mov->io); /* compositionStartTime */ + mov_buffer_r32(&mov->io); /* compositionEndTime */ + } + else + { + mov_buffer_r64(&mov->io); + mov_buffer_r64(&mov->io); + mov_buffer_r64(&mov->io); + mov_buffer_r64(&mov->io); + mov_buffer_r64(&mov->io); + } + + (void)box; + return mov_buffer_error(&mov->io); +} + +size_t mov_write_stts(const struct mov_t* mov, uint32_t count) +{ + uint32_t size, i; + const struct mov_sample_t* sample; + const struct mov_track_t* track = mov->track; + + size = 12/* full box */ + 4/* entry count */ + count * 8/* entry */; + + mov_buffer_w32(&mov->io, size); /* size */ + mov_buffer_write(&mov->io, "stts", 4); + mov_buffer_w32(&mov->io, 0); /* version & flags */ + mov_buffer_w32(&mov->io, count); /* entry count */ + + for (i = 0; i < track->sample_count; i++) + { + sample = &track->samples[i]; + if(0 == sample->first_chunk) + continue; + mov_buffer_w32(&mov->io, sample->first_chunk); // count + mov_buffer_w32(&mov->io, sample->samples_per_chunk); // delta * timescale / 1000 + } + + return size; +} + +size_t mov_write_ctts(const struct mov_t* mov, uint32_t count) +{ + uint32_t size, i; + const struct mov_sample_t* sample; + const struct mov_track_t* track = mov->track; + + size = 12/* full box */ + 4/* entry count */ + count * 8/* entry */; + + mov_buffer_w32(&mov->io, size); /* size */ + mov_buffer_write(&mov->io, "ctts", 4); + mov_buffer_w8(&mov->io, (track->flags & MOV_TRACK_FLAG_CTTS_V1) ? 1 : 0); /* version */ + mov_buffer_w24(&mov->io, 0); /* flags */ + mov_buffer_w32(&mov->io, count); /* entry count */ + + for (i = 0; i < track->sample_count; i++) + { + sample = &track->samples[i]; + if(0 == sample->first_chunk) + continue; + mov_buffer_w32(&mov->io, sample->first_chunk); // count + mov_buffer_w32(&mov->io, sample->samples_per_chunk); // offset * timescale / 1000 + } + + return size; +} + +uint32_t mov_build_stts(struct mov_track_t* track) +{ + size_t i; + uint32_t delta, count = 0; + struct mov_sample_t* sample = NULL; + + for (i = 0; i < track->sample_count; i++) + { + assert(track->samples[i + 1].dts >= track->samples[i].dts || i + 1 == track->sample_count); + delta = (uint32_t)(i + 1 < track->sample_count && track->samples[i + 1].dts > track->samples[i].dts ? track->samples[i + 1].dts - track->samples[i].dts : 1); + if (NULL != sample && (delta == sample->samples_per_chunk || i + 1 == track->sample_count) ) + { + track->samples[i].first_chunk = 0; + assert(sample->first_chunk > 0); + ++sample->first_chunk; // compress + } + else + { + sample = &track->samples[i]; + sample->first_chunk = 1; + sample->samples_per_chunk = delta; + ++count; + } + } + return count; +} + +uint32_t mov_build_ctts(struct mov_track_t* track) +{ + size_t i; + uint32_t delta; + uint32_t count = 0; + struct mov_sample_t* sample = NULL; + + for (i = 0; i < track->sample_count; i++) + { + delta = (uint32_t)(track->samples[i].pts - track->samples[i].dts); + if (i > 0 && delta == sample->samples_per_chunk) + { + track->samples[i].first_chunk = 0; + assert(sample->first_chunk > 0); + ++sample->first_chunk; // compress + } + else + { + sample = &track->samples[i]; + sample->first_chunk = 1; + sample->samples_per_chunk = delta; + ++count; + + // fixed: firefox version 51 don't support version 1 + if (track->samples[i].pts < track->samples[i].dts) + track->flags |= MOV_TRACK_FLAG_CTTS_V1; + } + } + + return count; +} + +void mov_apply_stts(struct mov_track_t* track) +{ + size_t i, j, n; + struct mov_stbl_t* stbl = &track->stbl; + + for (i = 0, n = 1; i < stbl->stts_count; i++) + { + for (j = 0; j < stbl->stts[i].sample_count; j++, n++) + { + track->samples[n].dts = track->samples[n - 1].dts + stbl->stts[i].sample_delta; + track->samples[n].pts = track->samples[n].dts; + } + } + assert(n - 1 == track->sample_count); // see more mov_read_stsz +} + +void mov_apply_ctts(struct mov_track_t* track) +{ + size_t i, j, n; + int32_t delta, dts_shift; + struct mov_stbl_t* stbl = &track->stbl; + + // make sure pts >= dts + dts_shift = 0; + for (i = 0; i < stbl->ctts_count; i++) + { + delta = (int32_t)stbl->ctts[i].sample_delta; + if (delta < 0 && dts_shift > delta && delta != -1 /* see more cslg box*/) + dts_shift = delta; + } + assert(dts_shift <= 0); + + // sample cts/pts + for (i = 0, n = 0; i < stbl->ctts_count; i++) + { + for (j = 0; j < stbl->ctts[i].sample_count; j++, n++) + track->samples[n].pts += (int64_t)((int32_t)stbl->ctts[i].sample_delta - dts_shift); // always as int, fixed mp4box delta version error + } + assert(0 == stbl->ctts_count || n == track->sample_count); +} diff --git a/MediaServer/libmov/source/mov-tag.c b/MediaServer/libmov/source/mov-tag.c new file mode 100644 index 0000000..869a249 --- /dev/null +++ b/MediaServer/libmov/source/mov-tag.c @@ -0,0 +1,63 @@ +#include "mov-internal.h" +#include + +struct mov_object_tag { + uint8_t id; + uint32_t tag; +}; + +static struct mov_object_tag s_tags[] = { + { MOV_OBJECT_H264, MOV_H264 }, // AVCSampleEntry (ISO/IEC 14496-15:2010) + { MOV_OBJECT_H264, MOV_TAG('a', 'v', 'c', '2') }, // AVC2SampleEntry (ISO/IEC 14496-15:2010) + { MOV_OBJECT_H264, MOV_TAG('a', 'v', 'c', '3') }, // AVCSampleEntry (ISO/IEC 14496-15:2017) + { MOV_OBJECT_H264, MOV_TAG('a', 'v', 'c', '4') }, // AVC2SampleEntry (ISO/IEC 14496-15:2017) + { MOV_OBJECT_H265, MOV_H265 }, // HEVCSampleEntry (ISO/IEC 14496-15:2013) + { MOV_OBJECT_H265, MOV_TAG('h', 'e', 'v', '1') }, // HEVCSampleEntry (ISO/IEC 14496-15:2013) + { MOV_OBJECT_H266, MOV_TAG('v', 'v', 'c', '1') }, // VVCSampleEntry (ISO/IEC 14496-15:2021) + { MOV_OBJECT_MP4V, MOV_MP4V }, + { MOV_OBJECT_JPEG, MOV_MP4V }, + { MOV_OBJECT_PNG, MOV_MP4V }, + { MOV_OBJECT_JPEG2000, MOV_MP4V }, + { MOV_OBJECT_AAC, MOV_MP4A }, + { MOV_OBJECT_MP3, MOV_MP4A }, // mp4_read_decoder_config_descriptor + { MOV_OBJECT_MP1A, MOV_MP4A }, // mp4_read_decoder_config_descriptor + { MOV_OBJECT_G711a, MOV_TAG('a', 'l', 'a', 'w') }, + { MOV_OBJECT_G711u, MOV_TAG('u', 'l', 'a', 'w') }, + { MOV_OBJECT_TEXT, MOV_TAG('t', 'x', '3', 'g') }, + { MOV_OBJECT_TEXT, MOV_TAG('t', 'e', 'x', 't') }, + { MOV_OBJECT_TEXT, MOV_TAG('c', '6', '0', '8') }, + { MOV_OBJECT_CHAPTER, MOV_TAG('t', 'e', 'x', 't') }, + { MOV_OBJECT_OPUS, MOV_OPUS }, + { MOV_OBJECT_VP8, MOV_VP8 }, + { MOV_OBJECT_VP9, MOV_VP9 }, + { MOV_OBJECT_AV1, MOV_AV1 }, + { MOV_OBJECT_AC3, MOV_AC3 }, + { MOV_OBJECT_EAC3, MOV_TAG('e', 'c', '-', '3') }, + { MOV_OBJECT_DTS, MOV_DTS }, + { MOV_OBJECT_VC1, MOV_VC1 }, + { MOV_OBJECT_DIRAC, MOV_DIRAC }, + + { MOV_OBJECT_H265, MOV_TAG('d', 'v', 'h', '1') }, // Dolby Vision HEVC(H.265) dvhe +}; + +uint32_t mov_object_to_tag(uint8_t object) +{ + int i; + for (i = 0; i < sizeof(s_tags) / sizeof(s_tags[0]); i++) + { + if (s_tags[i].id == object) + return s_tags[i].tag; + } + return 0; +} + +uint8_t mov_tag_to_object(uint32_t tag) +{ + int i; + for (i = 0; i < sizeof(s_tags) / sizeof(s_tags[0]); i++) + { + if (s_tags[i].tag == tag) + return s_tags[i].id; + } + return 0; +} diff --git a/MediaServer/libmov/source/mov-tfdt.c b/MediaServer/libmov/source/mov-tfdt.c new file mode 100644 index 0000000..2ed4865 --- /dev/null +++ b/MediaServer/libmov/source/mov-tfdt.c @@ -0,0 +1,48 @@ +#include "mov-internal.h" +#include +#include +#include +#include + +// 8.8.12 Track fragment decode time (p76) +int mov_read_tfdt(struct mov_t* mov, const struct mov_box_t* box) +{ + unsigned int version; + version = mov_buffer_r8(&mov->io); /* version */ + mov_buffer_r24(&mov->io); /* flags */ + + if (1 == version) + mov->track->tfdt_dts = mov_buffer_r64(&mov->io); /* baseMediaDecodeTime */ + else + mov->track->tfdt_dts = mov_buffer_r32(&mov->io); /* baseMediaDecodeTime */ + + // baseMediaDecodeTime + ELST start offset + mov_apply_elst_tfdt(mov->track); + + (void)box; + return mov_buffer_error(&mov->io); +} + +size_t mov_write_tfdt(const struct mov_t* mov) +{ + uint8_t version; + uint64_t baseMediaDecodeTime; + + if (mov->track->sample_count < 1) + return 0; + + baseMediaDecodeTime = mov->track->samples[0].dts - mov->track->start_dts; + version = baseMediaDecodeTime > INT32_MAX ? 1 : 0; + + mov_buffer_w32(&mov->io, 0 == version ? 16 : 20); /* size */ + mov_buffer_write(&mov->io, "tfdt", 4); + mov_buffer_w8(&mov->io, version); /* version */ + mov_buffer_w24(&mov->io, 0); /* flags */ + + if (1 == version) + mov_buffer_w64(&mov->io, baseMediaDecodeTime); /* baseMediaDecodeTime */ + else + mov_buffer_w32(&mov->io, (uint32_t)baseMediaDecodeTime); /* baseMediaDecodeTime */ + + return 0 == version ? 16 : 20; +} diff --git a/MediaServer/libmov/source/mov-tfhd.c b/MediaServer/libmov/source/mov-tfhd.c new file mode 100644 index 0000000..65d0ab7 --- /dev/null +++ b/MediaServer/libmov/source/mov-tfhd.c @@ -0,0 +1,98 @@ +#include "mov-internal.h" +#include + +// 8.8.7 Track Fragment Header Box (p71) +int mov_read_tfhd(struct mov_t* mov, const struct mov_box_t* box) +{ + uint32_t flags; + uint32_t track_ID; + + mov_buffer_r8(&mov->io); /* version */ + flags = mov_buffer_r24(&mov->io); /* flags */ + track_ID = mov_buffer_r32(&mov->io); /* track_ID */ + + mov->track = mov_find_track(mov, track_ID); + if (NULL == mov->track) + return -1; + + mov->track->tfhd.flags = flags; + + if (MOV_TFHD_FLAG_BASE_DATA_OFFSET & flags) + mov->track->tfhd.base_data_offset = mov_buffer_r64(&mov->io); /* base_data_offset*/ + else if(MOV_TFHD_FLAG_DEFAULT_BASE_IS_MOOF & flags) + mov->track->tfhd.base_data_offset = mov->moof_offset; /* default-base-is-moof */ + else + mov->track->tfhd.base_data_offset = mov->implicit_offset; + + if (MOV_TFHD_FLAG_SAMPLE_DESCRIPTION_INDEX & flags) + mov->track->tfhd.sample_description_index = mov_buffer_r32(&mov->io); /* sample_description_index*/ + else + mov->track->tfhd.sample_description_index = mov->track->trex.default_sample_description_index; + + if (MOV_TFHD_FLAG_DEFAULT_DURATION & flags) + mov->track->tfhd.default_sample_duration = mov_buffer_r32(&mov->io); /* default_sample_duration*/ + else + mov->track->tfhd.default_sample_duration = mov->track->trex.default_sample_duration; + + if (MOV_TFHD_FLAG_DEFAULT_SIZE & flags) + mov->track->tfhd.default_sample_size = mov_buffer_r32(&mov->io); /* default_sample_size*/ + else + mov->track->tfhd.default_sample_size = mov->track->trex.default_sample_size; + + if (MOV_TFHD_FLAG_DEFAULT_FLAGS & flags) + mov->track->tfhd.default_sample_flags = mov_buffer_r32(&mov->io); /* default_sample_flags*/ + else + mov->track->tfhd.default_sample_flags = mov->track->trex.default_sample_flags; + + if (MOV_TFHD_FLAG_DURATION_IS_EMPTY & flags) + (void)box; /* duration-is-empty*/ + return mov_buffer_error(&mov->io); +} + +size_t mov_write_tfhd(const struct mov_t* mov) +{ + size_t size; + uint64_t offset; + + size = 12 + 4 /* track_ID */; + + offset = mov_buffer_tell(&mov->io); + mov_buffer_w32(&mov->io, 0); /* size */ + mov_buffer_write(&mov->io, "tfhd", 4); + mov_buffer_w8(&mov->io, 0); /* version */ + mov_buffer_w24(&mov->io, mov->track->tfhd.flags); /* flags */ + mov_buffer_w32(&mov->io, mov->track->tkhd.track_ID); /* track_ID */ + + if (MOV_TFHD_FLAG_BASE_DATA_OFFSET & mov->track->tfhd.flags) + { + mov_buffer_w64(&mov->io, mov->track->tfhd.base_data_offset); /* base_data_offset*/ + size += 8; + } + + if (MOV_TFHD_FLAG_SAMPLE_DESCRIPTION_INDEX & mov->track->tfhd.flags) + { + mov_buffer_w32(&mov->io, mov->track->stsd.entries[0].data_reference_index); /* sample_description_index*/ + size += 4; + } + + if (MOV_TFHD_FLAG_DEFAULT_DURATION & mov->track->tfhd.flags) + { + mov_buffer_w32(&mov->io, mov->track->tfhd.default_sample_duration); /* default_sample_duration*/ + size += 4; + } + + if (MOV_TFHD_FLAG_DEFAULT_SIZE & mov->track->tfhd.flags) + { + mov_buffer_w32(&mov->io, mov->track->tfhd.default_sample_size); /* default_sample_size*/ + size += 4; + } + + if (MOV_TFHD_FLAG_DEFAULT_FLAGS & mov->track->tfhd.flags) + { + mov_buffer_w32(&mov->io, mov->track->tfhd.default_sample_flags); /* default_sample_flags*/ + size += 4; + } + + mov_write_size(mov, offset, size); + return size; +} diff --git a/MediaServer/libmov/source/mov-tfra.c b/MediaServer/libmov/source/mov-tfra.c new file mode 100644 index 0000000..3b80090 --- /dev/null +++ b/MediaServer/libmov/source/mov-tfra.c @@ -0,0 +1,90 @@ +#include "mov-internal.h" +#include +#include +#include + +// 8.8.10 Track Fragment Random Access Box (p74) +int mov_read_tfra(struct mov_t* mov, const struct mov_box_t* box) +{ + unsigned int version; + uint32_t track_ID; + uint32_t length_size_of; + uint32_t i, j, number_of_entry; + uint32_t traf_number, trun_number, sample_number; + struct mov_track_t* track; + + version = mov_buffer_r8(&mov->io); /* version */ + mov_buffer_r24(&mov->io); /* flags */ + track_ID = mov_buffer_r32(&mov->io); /* track_ID */ + + track = mov_find_track(mov, track_ID); + if (NULL == track) + { + mov_buffer_skip(&mov->io, box->size - 8); + return mov_buffer_error(&mov->io); + } + + length_size_of = mov_buffer_r32(&mov->io); /* length_size_of XXX */ + number_of_entry = mov_buffer_r32(&mov->io); /* number_of_entry */ + if (number_of_entry > 0) + { + void* p = realloc(track->frags, sizeof(struct mov_fragment_t) * number_of_entry); + if (!p) return -ENOMEM; + track->frags = p; + } + track->frag_count = number_of_entry; + + for (i = 0; i < number_of_entry; i++) + { + if (1 == version) + { + track->frags[i].time = mov_buffer_r64(&mov->io); /* time */ + track->frags[i].offset = mov_buffer_r64(&mov->io); /* moof_offset */ + } + else + { + track->frags[i].time = mov_buffer_r32(&mov->io); /* time */ + track->frags[i].offset = mov_buffer_r32(&mov->io); /* moof_offset */ + } + + for (traf_number = 0, j = 0; j < ((length_size_of >> 4) & 0x03) + 1; j++) + traf_number = (traf_number << 8) | mov_buffer_r8(&mov->io); /* traf_number */ + + for (trun_number = 0, j = 0; j < ((length_size_of >> 2) & 0x03) + 1; j++) + trun_number = (trun_number << 8) | mov_buffer_r8(&mov->io); /* trun_number */ + + for (sample_number = 0, j = 0; j < (length_size_of & 0x03) + 1; j++) + sample_number = (sample_number << 8) | mov_buffer_r8(&mov->io); /* sample_number */ + } + + (void)box; + return mov_buffer_error(&mov->io); +} + +size_t mov_write_tfra(const struct mov_t* mov) +{ + uint32_t i, size; + const struct mov_track_t* track = mov->track; + + size = 12/* full box */ + 12/* base */ + track->frag_count * 19/* index */; + + mov_buffer_w32(&mov->io, size); /* size */ + mov_buffer_write(&mov->io, "tfra", 4); + mov_buffer_w8(&mov->io, 1); /* version */ + mov_buffer_w24(&mov->io, 0); /* flags */ + + mov_buffer_w32(&mov->io, track->tkhd.track_ID); /* track_ID */ + mov_buffer_w32(&mov->io, 0); /* length_size_of_traf_num/trun/sample */ + mov_buffer_w32(&mov->io, track->frag_count); /* number_of_entry */ + + for (i = 0; i < track->frag_count; i++) + { + mov_buffer_w64(&mov->io, track->frags[i].time); + mov_buffer_w64(&mov->io, track->frags[i].offset); /* moof_offset */ + mov_buffer_w8(&mov->io, 1); /* traf number */ + mov_buffer_w8(&mov->io, 1); /* trun number */ + mov_buffer_w8(&mov->io, 1); /* sample number */ + } + + return size; +} diff --git a/MediaServer/libmov/source/mov-tkhd.c b/MediaServer/libmov/source/mov-tkhd.c new file mode 100644 index 0000000..d189a11 --- /dev/null +++ b/MediaServer/libmov/source/mov-tkhd.c @@ -0,0 +1,135 @@ +#include "mov-internal.h" +#include + +// ISO/IEC 14496-12:2012(E) +// 8.3.2 Track Header Box (p31) +// Box Type : 'tkhd' +// Container : Movie Box('trak') +// Mandatory : Yes +// Quantity : Exactly one + +/* +aligned(8) class TrackHeaderBox extends FullBox('tkhd', version, flags){ + if (version==1) { + unsigned int(64) creation_time; + unsigned int(64) modification_time; + unsigned int(32) track_ID; + const unsigned int(32) reserved = 0; + unsigned int(64) duration; + } else { // version==0 + unsigned int(32) creation_time; + unsigned int(32) modification_time; + unsigned int(32) track_ID; + const unsigned int(32) reserved = 0; + unsigned int(32) duration; + } + const unsigned int(32)[2] reserved = 0; + template int(16) layer = 0; + template int(16) alternate_group = 0; + template int(16) volume = {if track_is_audio 0x0100 else 0}; + const unsigned int(16) reserved = 0; + template int(32)[9] matrix= { 0x00010000,0,0,0,0x00010000,0,0,0,0x40000000 }; // unity matrix + unsigned int(32) width; + unsigned int(32) height; +} +*/ +int mov_read_tkhd(struct mov_t* mov, const struct mov_box_t* box) +{ + int i; + uint8_t version; + uint32_t flags; + uint64_t creation_time; + uint64_t modification_time; + uint64_t duration; + uint32_t track_ID; + struct mov_tkhd_t* tkhd; + struct mov_track_t* track; + + version = mov_buffer_r8(&mov->io); + flags = mov_buffer_r24(&mov->io); + + if (1 == version) + { + creation_time = mov_buffer_r64(&mov->io); + modification_time = mov_buffer_r64(&mov->io); + track_ID = mov_buffer_r32(&mov->io); + /*reserved = */mov_buffer_r32(&mov->io); + duration = mov_buffer_r64(&mov->io); + } + else + { + assert(0 == version); + creation_time = mov_buffer_r32(&mov->io); + modification_time = mov_buffer_r32(&mov->io); + track_ID = mov_buffer_r32(&mov->io); + /*reserved = */mov_buffer_r32(&mov->io); + duration = mov_buffer_r32(&mov->io); + } + mov_buffer_skip(&mov->io, 8); // const unsigned int(32)[2] reserved = 0; + + track = mov_fetch_track(mov, track_ID); + if (NULL == track) return -1; + + mov->track = track; + tkhd = &mov->track->tkhd; + tkhd->version = version; + tkhd->flags = flags; + tkhd->duration = duration; + tkhd->creation_time = creation_time; + tkhd->modification_time = modification_time; + + tkhd->layer = (int16_t)mov_buffer_r16(&mov->io); + tkhd->alternate_group = (int16_t)mov_buffer_r16(&mov->io); + tkhd->volume = (int16_t)mov_buffer_r16(&mov->io); + mov_buffer_skip(&mov->io, 2); // const unsigned int(16) reserved = 0; + for (i = 0; i < 9; i++) + tkhd->matrix[i] = mov_buffer_r32(&mov->io); + tkhd->width = mov_buffer_r32(&mov->io); + tkhd->height = mov_buffer_r32(&mov->io); + + (void)box; + return 0; +} + +size_t mov_write_tkhd(const struct mov_t* mov) +{ +// int rotation = 0; // 90/180/270 + uint16_t group = 0; + const struct mov_tkhd_t* tkhd = &mov->track->tkhd; + + mov_buffer_w32(&mov->io, 92); /* size */ + mov_buffer_write(&mov->io, "tkhd", 4); + mov_buffer_w8(&mov->io, 0); /* version */ + mov_buffer_w24(&mov->io, tkhd->flags); /* flags */ + + mov_buffer_w32(&mov->io, (uint32_t)tkhd->creation_time); /* creation_time */ + mov_buffer_w32(&mov->io, (uint32_t)tkhd->modification_time); /* modification_time */ + mov_buffer_w32(&mov->io, tkhd->track_ID); /* track_ID */ + mov_buffer_w32(&mov->io, 0); /* reserved */ + mov_buffer_w32(&mov->io, (uint32_t)tkhd->duration); /* duration */ + + mov_buffer_w32(&mov->io, 0); /* reserved */ + mov_buffer_w32(&mov->io, 0); /* reserved */ + mov_buffer_w16(&mov->io, tkhd->layer); /* layer */ + mov_buffer_w16(&mov->io, group); /* alternate_group */ + //mov_buffer_w16(&mov->io, AVSTREAM_AUDIO == track->stream_type ? 0x0100 : 0); /* volume */ + mov_buffer_w16(&mov->io, tkhd->volume); /* volume */ + mov_buffer_w16(&mov->io, 0); /* reserved */ + + // matrix + //for (i = 0; i < 9; i++) + // file_reader_rb32(mov->fp, tkhd->matrix[i]); + mov_buffer_w32(&mov->io, 0x00010000); /* u */ + mov_buffer_w32(&mov->io, 0); + mov_buffer_w32(&mov->io, 0); + mov_buffer_w32(&mov->io, 0); /* v */ + mov_buffer_w32(&mov->io, 0x00010000); + mov_buffer_w32(&mov->io, 0); + mov_buffer_w32(&mov->io, 0); /* w */ + mov_buffer_w32(&mov->io, 0); + mov_buffer_w32(&mov->io, 0x40000000); + + mov_buffer_w32(&mov->io, tkhd->width /*track->av.video.width * 0x10000U*/); /* width */ + mov_buffer_w32(&mov->io, tkhd->height/*track->av.video.height * 0x10000U*/); /* height */ + return 92; +} diff --git a/MediaServer/libmov/source/mov-track.c b/MediaServer/libmov/source/mov-track.c new file mode 100644 index 0000000..415bd4f --- /dev/null +++ b/MediaServer/libmov/source/mov-track.c @@ -0,0 +1,385 @@ +#include "mov-internal.h" +#include +#include +#include +#include + +#define FREE(p) do { if(p) free(p); } while(0) + +struct mov_track_t* mov_add_track(struct mov_t* mov) +{ + void* ptr = NULL; + struct mov_track_t* track; + + ptr = realloc(mov->tracks, sizeof(struct mov_track_t) * (mov->track_count + 1)); + if (NULL == ptr) return NULL; + + mov->tracks = ptr; + track = &mov->tracks[mov->track_count]; + memset(track, 0, sizeof(struct mov_track_t)); + track->start_dts = INT64_MIN; + track->last_dts = INT64_MIN; + + track->stsd.entries = calloc(1, sizeof(struct mov_sample_entry_t)); + if (NULL == track->stsd.entries) + return NULL; + track->stsd.current = track->stsd.entries; + + return track; +} + +void mov_free_track(struct mov_track_t* track) +{ + size_t i; + for (i = 0; i < track->sample_count; i++) + { + if (track->samples[i].data) + free(track->samples[i].data); + } + + for (i = 0; i < track->stsd.entry_count; i++) + { + if (track->stsd.entries[i].extra_data) + free(track->stsd.entries[i].extra_data); + } + + FREE(track->elst); + FREE(track->frags); + FREE(track->samples); +// FREE(track->extra_data); + FREE(track->stsd.entries); + FREE(track->stbl.stco); + FREE(track->stbl.stsc); + FREE(track->stbl.stss); + FREE(track->stbl.stts); + FREE(track->stbl.ctts); +} + +struct mov_track_t* mov_find_track(const struct mov_t* mov, uint32_t track) +{ + int i; + for (i = 0; i < mov->track_count; i++) + { + if (mov->tracks[i].tkhd.track_ID == track) + return mov->tracks + i; + } + return NULL; +} + +struct mov_track_t* mov_fetch_track(struct mov_t* mov, uint32_t track) +{ + struct mov_track_t* t; + t = mov_find_track(mov, track); + if (NULL == t) + { + t = mov_add_track(mov); + if (NULL != t) + { + ++mov->track_count; + t->tkhd.track_ID = track; + } + } + return t; +} + +int mov_add_audio(struct mov_track_t* track, const struct mov_mvhd_t* mvhd, uint32_t timescale, uint8_t object, int channel_count, int bits_per_sample, int sample_rate, const void* extra_data, size_t extra_data_size) +{ + struct mov_sample_entry_t* audio; + + if (MOV_OBJECT_MP3 == object && sample_rate > 24000) + object = MOV_OBJECT_MP1A; // use mpeg1 sample rate table, see more @libflv/source/mp3-header.c + + audio = &track->stsd.entries[0]; + audio->data_reference_index = 1; + audio->object_type_indication = object; + audio->stream_type = MP4_STREAM_AUDIO; + audio->u.audio.channelcount = (uint16_t)channel_count; + audio->u.audio.samplesize = (uint16_t)bits_per_sample; + audio->u.audio.samplerate = ((uint32_t)(sample_rate > 56635 ? 0 : sample_rate)) << 16; + + assert(0 != mov_object_to_tag(object)); + track->tag = mov_object_to_tag(object); + track->handler_type = MOV_AUDIO; + track->handler_descr = "SoundHandler"; + track->stsd.entry_count = 1; + track->offset = 0; + + track->tkhd.flags = MOV_TKHD_FLAG_TRACK_ENABLE | MOV_TKHD_FLAG_TRACK_IN_MOVIE; + track->tkhd.track_ID = mvhd->next_track_ID; + track->tkhd.creation_time = mvhd->creation_time; + track->tkhd.modification_time = mvhd->modification_time; + track->tkhd.width = 0; + track->tkhd.height = 0; + track->tkhd.volume = 0x0100; + track->tkhd.duration = 0; // placeholder + + track->mdhd.creation_time = track->tkhd.creation_time; + track->mdhd.modification_time = track->tkhd.modification_time; + track->mdhd.timescale = timescale; //sample_rate + track->mdhd.language = 0x55c4; + track->mdhd.duration = 0; // placeholder + + audio->extra_data = malloc(extra_data_size + 1); + if (NULL == audio->extra_data) + return -ENOMEM; + memcpy(audio->extra_data, extra_data, extra_data_size); + audio->extra_data_size = (int)extra_data_size; + + return 0; +} + +int mov_add_video(struct mov_track_t* track, const struct mov_mvhd_t* mvhd, uint32_t timescale, uint8_t object, int width, int height, const void* extra_data, size_t extra_data_size) +{ + struct mov_sample_entry_t* video; + + video = &track->stsd.entries[0]; + video->data_reference_index = 1; + video->object_type_indication = object; + video->stream_type = MP4_STREAM_VISUAL; + video->u.visual.width = (uint16_t)width; + video->u.visual.height = (uint16_t)height; + video->u.visual.depth = 0x0018; + video->u.visual.frame_count = 1; + video->u.visual.horizresolution = 0x00480000; + video->u.visual.vertresolution = 0x00480000; + + assert(0 != mov_object_to_tag(object)); + track->tag = mov_object_to_tag(object); + track->handler_type = MOV_VIDEO; + track->handler_descr = "VideoHandler"; + track->stsd.entry_count = 1; + track->offset = 0; + + track->tkhd.flags = MOV_TKHD_FLAG_TRACK_ENABLE | MOV_TKHD_FLAG_TRACK_IN_MOVIE; + track->tkhd.track_ID = mvhd->next_track_ID; + track->tkhd.creation_time = mvhd->creation_time; + track->tkhd.modification_time = mvhd->modification_time; + track->tkhd.width = width << 16; + track->tkhd.height = height << 16; + track->tkhd.volume = 0; + track->tkhd.duration = 0; // placeholder + + track->mdhd.creation_time = track->tkhd.creation_time; + track->mdhd.modification_time = track->tkhd.modification_time; + track->mdhd.timescale = timescale; //mov->mvhd.timescale + track->mdhd.language = 0x55c4; + track->mdhd.duration = 0; // placeholder + + video->extra_data = malloc(extra_data_size + 1); + if (NULL == video->extra_data) + return -ENOMEM; + memcpy(video->extra_data, extra_data, extra_data_size); + video->extra_data_size = (int)extra_data_size; + + return 0; +} + +int mov_add_subtitle(struct mov_track_t* track, const struct mov_mvhd_t* mvhd, uint32_t timescale, uint8_t object, const void* extra_data, size_t extra_data_size) +{ + static const uint8_t chapter_extra_data[] = { + // TextSampleEntry + 0x00, 0x00, 0x00, 0x01, // displayFlags + 0x00, 0x00, // horizontal + vertical justification + 0x00, 0x00, 0x00, 0x00, // bgColourRed/Green/Blue/Alpha + // BoxRecord + 0x00, 0x00, 0x00, 0x00, // defTextBoxTop/Left + 0x00, 0x00, 0x00, 0x00, // defTextBoxBottom/Right + // StyleRecord + 0x00, 0x00, 0x00, 0x00, // startChar + endChar + 0x00, 0x01, // fontID + 0x00, 0x00, // fontStyleFlags + fontSize + 0x00, 0x00, 0x00, 0x00, // fgColourRed/Green/Blue/Alpha + // FontTableBox + 0x00, 0x00, 0x00, 0x0D, // box size + 'f', 't', 'a', 'b', // box atom name + 0x00, 0x01, // entry count + // FontRecord + 0x00, 0x01, // font ID + 0x00, // font name length + }; + + struct mov_sample_entry_t* subtitle; + + subtitle = &track->stsd.entries[0]; + subtitle->data_reference_index = 1; + subtitle->object_type_indication = object; + subtitle->stream_type = MP4_STREAM_VISUAL; // Visually composed tracks including video and text are layered using the 'layer' value. + + assert(0 != mov_object_to_tag(object)); + track->tag = mov_object_to_tag(object); + track->handler_type = track->tag == MOV_TAG('t', 'e', 'x', 't') ? MOV_TEXT : MOV_SBTL; + track->handler_descr = "SubtitleHandler"; + track->stsd.entry_count = 1; + track->offset = 0; + + track->tkhd.flags = (track->tag == MOV_TAG('t', 'e', 'x', 't') ? 0 : MOV_TKHD_FLAG_TRACK_ENABLE) | MOV_TKHD_FLAG_TRACK_IN_MOVIE; + track->tkhd.track_ID = mvhd->next_track_ID; + track->tkhd.creation_time = mvhd->creation_time; + track->tkhd.modification_time = mvhd->modification_time; + track->tkhd.width = 0; + track->tkhd.height = 0; + track->tkhd.volume = 0; + track->tkhd.duration = 0; // placeholder + + track->mdhd.creation_time = track->tkhd.creation_time; + track->mdhd.modification_time = track->tkhd.modification_time; + track->mdhd.timescale = timescale; + track->mdhd.language = 0x55c4; + track->mdhd.duration = 0; // placeholder + + if (object == MOV_OBJECT_CHAPTER && 0 == extra_data_size) + { + extra_data = chapter_extra_data; + extra_data_size = sizeof(chapter_extra_data); + } + + subtitle->extra_data = malloc(extra_data_size + 1); + if (NULL == subtitle->extra_data) + return -ENOMEM; + memcpy(subtitle->extra_data, extra_data, extra_data_size); + subtitle->extra_data_size = (int)extra_data_size; + + return 0; +} + +// ISO/IEC 14496-12:2012(E) 6.2.3 Box Order (p23) +// It is recommended that the boxes within the Sample Table Box be in the following order: +// Sample Description, Time to Sample, Sample to Chunk, Sample Size, Chunk Offset. +size_t mov_write_stbl(const struct mov_t* mov) +{ + size_t size; + uint32_t count; + uint64_t offset; + struct mov_track_t* track; + track = (struct mov_track_t*)mov->track; + + size = 8 /* Box */; + offset = mov_buffer_tell(&mov->io); + mov_buffer_w32(&mov->io, 0); /* size */ + mov_buffer_write(&mov->io, "stbl", 4); + + size += mov_write_stsd(mov); + + count = mov_build_stts(track); + size += mov_write_stts(mov, count); + if (track->tkhd.width > 0 && track->tkhd.height > 0) + size += mov_write_stss(mov); // video only + count = mov_build_ctts(track); + if (track->sample_count > 0 && (count > 1 || track->samples[0].samples_per_chunk != 0)) + size += mov_write_ctts(mov, count); + + count = mov_build_stco(track); + size += mov_write_stsc(mov); + size += mov_write_stsz(mov); + size += mov_write_stco(mov, count); + + mov_write_size(mov, offset, size); /* update size */ + return size; +} + +size_t mov_write_minf(const struct mov_t* mov) +{ + size_t size; + uint64_t offset; + const struct mov_track_t* track = mov->track; + + size = 8 /* Box */; + offset = mov_buffer_tell(&mov->io); + mov_buffer_w32(&mov->io, 0); /* size */ + mov_buffer_write(&mov->io, "minf", 4); + + if (MOV_VIDEO == track->handler_type) + { + size += mov_write_vmhd(mov); + } + else if (MOV_AUDIO == track->handler_type) + { + size += mov_write_smhd(mov); + } + else if (MOV_TEXT == track->handler_type) + { + size += mov_write_gmhd(mov); + } + else if (MOV_SUBT == track->handler_type || MOV_SBTL == track->handler_type) + { + size += mov_write_nmhd(mov); + } + else + { + assert(0); + } + + size += mov_write_dinf(mov); + size += mov_write_stbl(mov); + mov_write_size(mov, offset, size); /* update size */ + return size; +} + +size_t mov_write_mdia(const struct mov_t* mov) +{ + size_t size; + uint64_t offset; + + size = 8 /* Box */; + offset = mov_buffer_tell(&mov->io); + mov_buffer_w32(&mov->io, 0); /* size */ + mov_buffer_write(&mov->io, "mdia", 4); + + size += mov_write_mdhd(mov); + size += mov_write_hdlr(mov); + size += mov_write_minf(mov); + + mov_write_size(mov, offset, size); /* update size */ + return size; +} + +size_t mov_write_trak(const struct mov_t* mov) +{ + size_t size; + uint64_t offset; + + size = 8 /* Box */; + offset = mov_buffer_tell(&mov->io); + mov_buffer_w32(&mov->io, 0); /* size */ + mov_buffer_write(&mov->io, "trak", 4); + + size += mov_write_tkhd(mov); + size += mov_write_edts(mov); + if(mov->track->chpl_track != 0) + size += mov_write_tref(mov); + size += mov_write_mdia(mov); + + mov_write_size(mov, offset, size); /* update size */ + return size; +} + +size_t mov_write_edts(const struct mov_t* mov) +{ + size_t size; + uint64_t offset; + + if (mov->track->sample_count < 1) + return 0; + + size = 8 /* Box */; + offset = mov_buffer_tell(&mov->io); + mov_buffer_w32(&mov->io, 0); /* size */ + mov_buffer_write(&mov->io, "edts", 4); + + size += mov_write_elst(mov); + + mov_write_size(mov, offset, size); /* update size */ + return size; +} + +size_t mov_write_tref(const struct mov_t* mov) +{ + mov_buffer_w32(&mov->io, 20); /* size */ + mov_buffer_write(&mov->io, "tref", 4); + + mov_buffer_w32(&mov->io, 12); /* size */ + mov_buffer_write(&mov->io, "chap", 4); + mov_buffer_w32(&mov->io, mov->track->chpl_track); + + return 20; +} diff --git a/MediaServer/libmov/source/mov-trex.c b/MediaServer/libmov/source/mov-trex.c new file mode 100644 index 0000000..b6130d6 --- /dev/null +++ b/MediaServer/libmov/source/mov-trex.c @@ -0,0 +1,37 @@ +#include "mov-internal.h" +#include +#include +#include + +// 8.8.3 Track Extends Box (p69) +int mov_read_trex(struct mov_t* mov, const struct mov_box_t* box) +{ + uint32_t track_ID; + struct mov_track_t* track; + + (void)box; + mov_buffer_r32(&mov->io); /* version & flags */ + track_ID = mov_buffer_r32(&mov->io); /* track_ID */ + + track = mov_fetch_track(mov, track_ID); + if (NULL == track) return -1; + + track->trex.default_sample_description_index = mov_buffer_r32(&mov->io); /* default_sample_description_index */ + track->trex.default_sample_duration = mov_buffer_r32(&mov->io); /* default_sample_duration */ + track->trex.default_sample_size = mov_buffer_r32(&mov->io); /* default_sample_size */ + track->trex.default_sample_flags = mov_buffer_r32(&mov->io); /* default_sample_flags */ + return mov_buffer_error(&mov->io); +} + +size_t mov_write_trex(const struct mov_t* mov) +{ + mov_buffer_w32(&mov->io, 12 + 20); /* size */ + mov_buffer_write(&mov->io, "trex", 4); + mov_buffer_w32(&mov->io, 0); /* version & flags */ + mov_buffer_w32(&mov->io, mov->track->tkhd.track_ID); /* track_ID */ + mov_buffer_w32(&mov->io, 1); /* default_sample_description_index */ + mov_buffer_w32(&mov->io, 0); /* default_sample_duration */ + mov_buffer_w32(&mov->io, 0); /* default_sample_size */ + mov_buffer_w32(&mov->io, 0); /* default_sample_flags */ + return 32; +} diff --git a/MediaServer/libmov/source/mov-trun.c b/MediaServer/libmov/source/mov-trun.c new file mode 100644 index 0000000..fa03653 --- /dev/null +++ b/MediaServer/libmov/source/mov-trun.c @@ -0,0 +1,163 @@ +#include "mov-internal.h" +#include +#include +#include +#include + +// 8.8.8 Track Fragment Run Box (p72) +int mov_read_trun(struct mov_t* mov, const struct mov_box_t* box) +{ + unsigned int version; + uint32_t flags; + uint32_t i, sample_count; + uint64_t data_offset; + uint32_t first_sample_flags; + uint32_t sample_duration, sample_size, sample_flags; + int64_t sample_composition_time_offset; + struct mov_track_t* track; + struct mov_sample_t* sample; + + version = mov_buffer_r8(&mov->io); /* version */ + flags = mov_buffer_r24(&mov->io); /* flags */ + sample_count = mov_buffer_r32(&mov->io); /* sample_count */ + + track = mov->track; + if (sample_count > 0) + { + void* p = realloc(track->samples, sizeof(struct mov_sample_t) * (track->sample_count + sample_count + 1)); + if (NULL == p) return -ENOMEM; + track->samples = (struct mov_sample_t*)p; + memset(track->samples + track->sample_count, 0, sizeof(struct mov_sample_t) * (sample_count + 1)); + } + + data_offset = track->tfhd.base_data_offset; + if (MOV_TRUN_FLAG_DATA_OFFSET_PRESENT & flags) + data_offset += (int32_t)mov_buffer_r32(&mov->io); /* data_offset */ + + if (MOV_TRUN_FLAG_FIRST_SAMPLE_FLAGS_PRESENT & flags) + first_sample_flags = mov_buffer_r32(&mov->io); /* first_sample_flags */ + else + first_sample_flags = track->tfhd.flags; + + sample = track->samples + track->sample_count; + for (i = 0; i < sample_count; i++) + { + if (MOV_TRUN_FLAG_SAMPLE_DURATION_PRESENT & flags) + sample_duration = mov_buffer_r32(&mov->io); /* sample_duration*/ + else + sample_duration = track->tfhd.default_sample_duration; + + if (MOV_TRUN_FLAG_SAMPLE_SIZE_PRESENT & flags) + sample_size = mov_buffer_r32(&mov->io); /* sample_size*/ + else + sample_size = track->tfhd.default_sample_size; + + if (MOV_TRUN_FLAG_SAMPLE_FLAGS_PRESENT & flags) + sample_flags = mov_buffer_r32(&mov->io); /* sample_flags*/ + else + sample_flags = i ? track->tfhd.default_sample_flags : first_sample_flags; + + if (MOV_TRUN_FLAG_SAMPLE_COMPOSITION_TIME_OFFSET_PRESENT & flags) + { + sample_composition_time_offset = mov_buffer_r32(&mov->io); /* sample_composition_time_offset*/ + if (1 == version) + sample_composition_time_offset = (int32_t)sample_composition_time_offset; + } + else + sample_composition_time_offset = 0; + + sample[i].offset = data_offset; + sample[i].bytes = sample_size; + sample[i].dts = track->tfdt_dts; + sample[i].pts = sample[i].dts + sample_composition_time_offset; + sample[i].flags = (sample_flags & (MOV_TREX_FLAG_SAMPLE_IS_NO_SYNC_SAMPLE | 0x01000000)) ? 0 : MOV_AV_FLAG_KEYFREAME; + sample[i].sample_description_index = track->tfhd.sample_description_index; + + data_offset += sample_size; + track->tfdt_dts += sample_duration; + } + track->sample_count += sample_count; + mov->implicit_offset = data_offset; + + (void)box; + return mov_buffer_error(&mov->io); +} + +size_t mov_write_trun(const struct mov_t* mov, uint32_t from, uint32_t count, uint32_t moof) +{ + uint32_t flags; + uint32_t delta; + uint64_t offset; + uint32_t size, i; + const struct mov_sample_t* sample; + const struct mov_track_t* track = mov->track; + + if (count < 1) return 0; + assert(from + count <= track->sample_count); + flags = MOV_TRUN_FLAG_DATA_OFFSET_PRESENT; + if (track->samples[from].flags & MOV_AV_FLAG_KEYFREAME) + flags |= MOV_TRUN_FLAG_FIRST_SAMPLE_FLAGS_PRESENT; + + for (i = from; i < from + count; i++) + { + sample = track->samples + i; + if (sample->bytes != track->tfhd.default_sample_size) + flags |= MOV_TRUN_FLAG_SAMPLE_SIZE_PRESENT; + if ((uint32_t)(i + 1 < track->sample_count ? track->samples[i + 1].dts - track->samples[i].dts : track->turn_last_duration) != track->tfhd.default_sample_duration) + flags |= MOV_TRUN_FLAG_SAMPLE_DURATION_PRESENT; + if (sample->pts != sample->dts) + flags |= MOV_TRUN_FLAG_SAMPLE_COMPOSITION_TIME_OFFSET_PRESENT; + } + + size = 12/* full box */ + 4/* sample count */; + + offset = mov_buffer_tell(&mov->io); + mov_buffer_w32(&mov->io, 0); /* size */ + mov_buffer_write(&mov->io, "trun", 4); + mov_buffer_w8(&mov->io, 1); /* version */ + mov_buffer_w24(&mov->io, flags); /* flags */ + mov_buffer_w32(&mov->io, count); /* sample_count */ + + assert(flags & MOV_TRUN_FLAG_DATA_OFFSET_PRESENT); + if (flags & MOV_TRUN_FLAG_DATA_OFFSET_PRESENT) + { + mov_buffer_w32(&mov->io, moof + (uint32_t)track->samples[from].offset); + size += 4; + } + + if (flags & MOV_TRUN_FLAG_FIRST_SAMPLE_FLAGS_PRESENT) + { + mov_buffer_w32(&mov->io, MOV_TREX_FLAG_SAMPLE_DEPENDS_ON_I_PICTURE); /* first_sample_flags */ + size += 4; + } + + assert(from + count <= track->sample_count); + for (i = from; i < from + count; i++) + { + sample = track->samples + i; + if (flags & MOV_TRUN_FLAG_SAMPLE_DURATION_PRESENT) + { + delta = (uint32_t)(i + 1 < track->sample_count ? track->samples[i + 1].dts - track->samples[i].dts : track->turn_last_duration); + mov_buffer_w32(&mov->io, delta); /* sample_duration */ + size += 4; + } + + if (flags & MOV_TRUN_FLAG_SAMPLE_SIZE_PRESENT) + { + mov_buffer_w32(&mov->io, (uint32_t)sample->bytes); /* sample_size */ + size += 4; + } + + assert(0 == (flags & MOV_TRUN_FLAG_SAMPLE_FLAGS_PRESENT)); +// mov_buffer_w32(&mov->io, 0); /* sample_flags */ + + if (flags & MOV_TRUN_FLAG_SAMPLE_COMPOSITION_TIME_OFFSET_PRESENT) + { + mov_buffer_w32(&mov->io, (int32_t)(sample->pts - sample->dts)); /* sample_composition_time_offset */ + size += 4; + } + } + + mov_write_size(mov, offset, size); + return size; +} diff --git a/MediaServer/libmov/source/mov-tx3g.c b/MediaServer/libmov/source/mov-tx3g.c new file mode 100644 index 0000000..01ed3a1 --- /dev/null +++ b/MediaServer/libmov/source/mov-tx3g.c @@ -0,0 +1,115 @@ +#include "mov-internal.h" +#include +#include +#include + +// 3GPP TS 26.245 Release 14 12 V14.0.0 (2017-03) +/* +aligned(8) class StyleRecord { + unsigned int(16) startChar; + unsigned int(16) endChar; + unsigned int(16) font-ID; + unsigned int(8) face-style-flags; + unsigned int(8) font-size; + unsigned int(8) text-color-rgba[4]; +} + +class FontRecord { + unsigned int(16) font-ID; + unsigned int(8) font-name-length; + unsigned int(8) font[font-name-length]; +} +class FontTableBox() extends Box('ftab') { + unsigned int(16) entry-count; + FontRecord font-entry[entry-count]; +} +class DisparityBox() extends TextSampleModifierBox ('disp') { + signed int(16) disparity-shift-in-16th-pel; +} +class BoxRecord { + signed int(16) top; + signed int(16) left; + signed int(16) bottom; + signed int(16) right; +} +class TextSampleEntry() extends SampleEntry ('tx3g') { + unsigned int(32) displayFlags; + signed int(8) horizontal-justification; + signed int(8) vertical-justification; + unsigned int(8) background-color-rgba[4]; + BoxRecord default-text-box; + StyleRecord default-style; + FontTableBox font-table; + DisparityBox default-disparity; +} +*/ + +int mov_read_tx3g(struct mov_t* mov, const struct mov_box_t* box) +{ + struct mov_box_t extra; + //struct mov_track_t* track = mov->track; + //struct mov_sample_entry_t* entry = track->stsd.current; + + mov_buffer_r32(&mov->io); // displayFlags + mov_buffer_r8(&mov->io); // horizontal-justification + mov_buffer_r8(&mov->io); // vertical-justification + mov_buffer_r8(&mov->io); // background-color-rgba[4] + mov_buffer_r8(&mov->io); + mov_buffer_r8(&mov->io); + mov_buffer_r8(&mov->io); + mov_buffer_r16(&mov->io); // BoxRecord.top + mov_buffer_r16(&mov->io); // BoxRecord.left + mov_buffer_r16(&mov->io); // BoxRecord.bottom + mov_buffer_r16(&mov->io); // BoxRecord.right + mov_buffer_r16(&mov->io); // StyleRecord.startChar + mov_buffer_r16(&mov->io); // StyleRecord.endChar + mov_buffer_r16(&mov->io); // StyleRecord.font-ID + mov_buffer_r8(&mov->io); // StyleRecord.face-style-flags + mov_buffer_r8(&mov->io); // StyleRecord.font-size + mov_buffer_r8(&mov->io); // StyleRecord.text-color-rgba[4] + mov_buffer_r8(&mov->io); + mov_buffer_r8(&mov->io); + mov_buffer_r8(&mov->io); + + // FontTableBox + extra.type = box->type; + extra.size = box->size - 30; + return mp4_read_extra(mov, &extra); +} + +size_t mov_write_tx3g(const struct mov_t* mov) +{ + //const struct mov_track_t* track = mov->track; + //const struct mov_sample_entry_t* entry = track->stsd.current; + + mov_buffer_w32(&mov->io, 0); // displayFlags + mov_buffer_w8(&mov->io, 0x01); // horizontal-justification + mov_buffer_w8(&mov->io, 0xFF); // vertical-justification + mov_buffer_w8(&mov->io, 0x00); // background-color-rgba[4] + mov_buffer_w8(&mov->io, 0x00); + mov_buffer_w8(&mov->io, 0x00); + mov_buffer_w8(&mov->io, 0x00); + mov_buffer_w16(&mov->io, 0x0000); // BoxRecord.top + mov_buffer_w16(&mov->io, 0x0000); // BoxRecord.left + mov_buffer_w16(&mov->io, 0x0000); // BoxRecord.bottom + mov_buffer_w16(&mov->io, 0x0000); // BoxRecord.right + mov_buffer_w16(&mov->io, 0x0000); // StyleRecord.startChar + mov_buffer_w16(&mov->io, 0x0000); // StyleRecord.endChar + mov_buffer_w16(&mov->io, 0x0001); // StyleRecord.font-ID + mov_buffer_w8(&mov->io, 0x00); // StyleRecord.face-style-flags + mov_buffer_w8(&mov->io, 0x12); // StyleRecord.font-size + mov_buffer_w8(&mov->io, 0xFF); // StyleRecord.text-color-rgba[4] + mov_buffer_w8(&mov->io, 0xFF); + mov_buffer_w8(&mov->io, 0xFF); + mov_buffer_w8(&mov->io, 0xFF); + + // FontTableBox + mov_buffer_w32(&mov->io, 18); /* size */ + mov_buffer_write(&mov->io, "ftab", 4); + mov_buffer_w16(&mov->io, 1); /* entry-count */ + mov_buffer_w16(&mov->io, 0x0001); /* FontRecord.font-ID */ + mov_buffer_w8(&mov->io, 5); /* FontRecord.font-name-length */ + mov_buffer_write(&mov->io, "Serif", 5); /* FontRecord.font[font-name-length] */ + + return 30 + 18; +} diff --git a/MediaServer/libmov/source/mov-udta.c b/MediaServer/libmov/source/mov-udta.c new file mode 100644 index 0000000..5129b76 --- /dev/null +++ b/MediaServer/libmov/source/mov-udta.c @@ -0,0 +1,148 @@ +#include "mov-udta.h" +#include "mov-ioutil.h" +#include "mov-memory-buffer.h" +#include "mov-internal.h" + +int mov_read_udta(struct mov_t* mov, const struct mov_box_t* box) +{ + mov_buffer_skip(&mov->io, box->size); + return mov_buffer_error(&mov->io); +} + +size_t mov_write_udta(const struct mov_t* mov) +{ + if (!mov->udta || mov->udta_size < 1) + return 0; + + mov_buffer_w32(&mov->io, 8 + (uint32_t)mov->udta_size); + mov_buffer_write(&mov->io, "udta", 4); + mov_buffer_write(&mov->io, mov->udta, mov->udta_size); + return 8 + (size_t)mov->udta_size; +} + +int mov_udta_meta_write(const struct mov_udta_meta_t* meta, void* data, int bytes) +{ + struct mov_ioutil_t w; + struct mov_memory_buffer_t ptr; + uint64_t pmeta, pilst, n; + + ptr.bytes = 0; + ptr.maxsize = bytes; + ptr.capacity = bytes; + ptr.off = 0; + ptr.ptr = (uint8_t*)data; + memset(&w, 0, sizeof(w)); + memcpy(&w.io, mov_memory_buffer(), sizeof(w.io)); + w.param = &ptr; + + pmeta = mov_buffer_tell(&w); + mov_buffer_w32(&w, 0); // placeholder + mov_buffer_write(&w, "meta", 4); + mov_buffer_w32(&w, 0); /* version & flags */ + + mov_buffer_w32(&w, 33); + mov_buffer_write(&w, "hdlr", 4); + mov_buffer_w32(&w, 0); /* version & flags */ + mov_buffer_w32(&w, 0); + mov_buffer_write(&w, "mdir", 4); + mov_buffer_write(&w, "appl", 4); + mov_buffer_w32(&w, 0); + mov_buffer_w32(&w, 0); + mov_buffer_w8(&w, 0); + + pilst = mov_buffer_tell(&w); + mov_buffer_w32(&w, 0); // placeholder + mov_buffer_write(&w, "ilst", 4); + + // write cover + mov_buffer_w32(&w, meta->cover_size + 16 + 8); + mov_buffer_write(&w, "covr", 4); + mov_buffer_w32(&w, meta->cover_size + 16); + mov_buffer_write(&w, "data", 4); + mov_buffer_w32(&w, 0); // TODO track tag + mov_buffer_w32(&w, 0); + mov_buffer_write(&w, meta->cover, meta->cover_size); + + // update box size + n = mov_buffer_tell(&w); + mov_buffer_seek(&w, pilst); + mov_buffer_w32(&w, (uint32_t)(n - pilst)); + mov_buffer_seek(&w, pmeta); + mov_buffer_w32(&w, (uint32_t)(n - pmeta)); + mov_buffer_seek(&w, n); // rewind + + return (int)ptr.bytes; +} + +int mov_udta_chapter_write(const struct mov_udta_chapter_t* chapters, int count, void* data, int bytes) +{ + struct mov_ioutil_t w; + struct mov_memory_buffer_t ptr; + uint64_t pmeta, pilst, pchpl, n; + int i; + static const uint8_t ilst[] = { 0x00, 0x00, 0x00, 0x25, 0xa9, 0x74, 0x6f, 0x6f, 0x00, 0x00, 0x00, 0x1d, 0x64, 0x61, 0x74, 0x61, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x4c, 0x61, 0x76, 0x66, 0x36, 0x30, 0x2e, 0x32, 0x30, 0x2e, 0x31, 0x30, 0x30 }; + + ptr.bytes = 0; + ptr.maxsize = bytes; + ptr.capacity = bytes; + ptr.off = 0; + ptr.ptr = (uint8_t*)data; + memset(&w, 0, sizeof(w)); + memcpy(&w.io, mov_memory_buffer(), sizeof(w.io)); + w.param = &ptr; + + pmeta = mov_buffer_tell(&w); + mov_buffer_w32(&w, 0); // placeholder + mov_buffer_write(&w, "meta", 4); + mov_buffer_w32(&w, 0); /* version & flags */ + + mov_buffer_w32(&w, 33); + mov_buffer_write(&w, "hdlr", 4); + mov_buffer_w32(&w, 0); /* version & flags */ + mov_buffer_w32(&w, 0); + mov_buffer_write(&w, "mdir", 4); + mov_buffer_write(&w, "appl", 4); + mov_buffer_w32(&w, 0); + mov_buffer_w32(&w, 0); + mov_buffer_w8(&w, 0); + + mov_buffer_w32(&w, 0x2d); // placeholder + mov_buffer_write(&w, "ilst", 4); + mov_buffer_write(&w, ilst, sizeof(ilst)); + + // update meta box size + n = mov_buffer_tell(&w); + mov_buffer_seek(&w, pmeta); + mov_buffer_w32(&w, (uint32_t)(n - pmeta)); + mov_buffer_seek(&w, n); // rewind + + pchpl = mov_buffer_tell(&w); + mov_buffer_w32(&w, 0); // placeholder + mov_buffer_write(&w, "chpl", 4); + mov_buffer_w8(&w, 1); /* version */ + mov_buffer_w24(&w, 0); /* flags */ + mov_buffer_w32(&w, 0); /* unknown*/ + + mov_buffer_w8(&w, (uint8_t)count); + for (i = 0; i < count; i++) + { + mov_buffer_w64(&w, chapters[i].timestamp); + if (chapters[i].title && *chapters[i].title && strlen(chapters[i].title) <= 255) + { + mov_buffer_w8(&w, (uint8_t)strlen(chapters[i].title)); + mov_buffer_write(&w, chapters[i].title, strlen(chapters[i].title)); + } + else + { + mov_buffer_w8(&w, 0); + } + } + + // update chpl box size + n = mov_buffer_tell(&w); + mov_buffer_seek(&w, pchpl); + mov_buffer_w32(&w, (uint32_t)(n - pchpl)); + mov_buffer_seek(&w, n); // rewind + + return (int)ptr.bytes; +} diff --git a/MediaServer/libmov/source/mov-vpcc.c b/MediaServer/libmov/source/mov-vpcc.c new file mode 100644 index 0000000..a510069 --- /dev/null +++ b/MediaServer/libmov/source/mov-vpcc.c @@ -0,0 +1,40 @@ +#include "mov-internal.h" +#include +#include +#include + +// https://www.webmproject.org/vp9/mp4/ +// extra data: VPCodecConfigurationBox + +int mov_read_vpcc(struct mov_t* mov, const struct mov_box_t* box) +{ + struct mov_track_t* track = mov->track; + struct mov_sample_entry_t* entry = track->stsd.current; + if(box->size < 4) + return -1; + if (entry->extra_data_size < box->size-4) + { + void* p = realloc(entry->extra_data, (size_t)box->size-4); + if (NULL == p) return -ENOMEM; + entry->extra_data = p; + } + + mov_buffer_r8(&mov->io); /* version */ + mov_buffer_r24(&mov->io); /* flags */ + mov_buffer_read(&mov->io, entry->extra_data, box->size-4); + entry->extra_data_size = (int)box->size - 4; + return mov_buffer_error(&mov->io); +} + +size_t mov_write_vpcc(const struct mov_t* mov) +{ + const struct mov_track_t* track = mov->track; + const struct mov_sample_entry_t* entry = track->stsd.current; + mov_buffer_w32(&mov->io, entry->extra_data_size + 12); /* size */ + mov_buffer_write(&mov->io, "vpcC", 4); + mov_buffer_w8(&mov->io, 1); /* version */ + mov_buffer_w24(&mov->io, 0); /* flags */ + if (entry->extra_data_size > 0) + mov_buffer_write(&mov->io, entry->extra_data, entry->extra_data_size); + return entry->extra_data_size + 12; +} diff --git a/MediaServer/libmov/source/mov-writer.c b/MediaServer/libmov/source/mov-writer.c new file mode 100644 index 0000000..2d3df74 --- /dev/null +++ b/MediaServer/libmov/source/mov-writer.c @@ -0,0 +1,343 @@ +#include "mov-writer.h" +#include "mov-internal.h" +#include +#include +#include +#include +#include + +struct mov_writer_t +{ + struct mov_t mov; + uint64_t mdat_size; + uint64_t mdat_offset; +}; + +static int mov_write_tail(struct mov_t* mov) +{ + mov_buffer_w32(&mov->io, 8 + strlen(MOV_APP)); /* size */ + mov_buffer_write(&mov->io, "free", 4); + mov_buffer_write(&mov->io, MOV_APP, strlen(MOV_APP)); + return 0; +} + +static size_t mov_write_moov(struct mov_t* mov) +{ + int i; + size_t size; + uint64_t offset; + + size = 8 /* Box */; + offset = mov_buffer_tell(&mov->io); + mov_buffer_w32(&mov->io, 0); /* size */ + mov_buffer_write(&mov->io, "moov", 4); + + size += mov_write_mvhd(mov); +// size += mov_write_iods(mov); + for(i = 0; i < mov->track_count; i++) + { + mov->track = mov->tracks + i; + if (mov->track->sample_count < 1) + continue; + size += mov_write_trak(mov); + } + + size += mov_write_udta(mov); + mov_write_size(mov, offset, size); /* update size */ + return size; +} + +void mov_write_size(const struct mov_t* mov, uint64_t offset, size_t size) +{ + uint64_t offset2; + assert(size < UINT32_MAX); + offset2 = mov_buffer_tell(&mov->io); + mov_buffer_seek(&mov->io, offset); + mov_buffer_w32(&mov->io, (uint32_t)size); + mov_buffer_seek(&mov->io, offset2); +} + +static int mov_writer_init(struct mov_t* mov) +{ + mov->ftyp.major_brand = MOV_BRAND_ISOM; + mov->ftyp.minor_version = 0x200; + mov->ftyp.brands_count = 4; + mov->ftyp.compatible_brands[0] = MOV_BRAND_ISOM; + mov->ftyp.compatible_brands[1] = MOV_BRAND_ISO2; + mov->ftyp.compatible_brands[2] = MOV_BRAND_AVC1; + mov->ftyp.compatible_brands[3] = MOV_BRAND_MP41; + mov->header = 0; + return 0; +} + +struct mov_writer_t* mov_writer_create(const struct mov_buffer_t* buffer, void* param, int flags) +{ + struct mov_t* mov; + struct mov_writer_t* writer; + writer = (struct mov_writer_t*)calloc(1, sizeof(struct mov_writer_t)); + if (NULL == writer) + return NULL; + + mov = &writer->mov; + mov->flags = flags; + mov->io.param = param; + memcpy(&mov->io.io, buffer, sizeof(mov->io.io)); + + mov->mvhd.next_track_ID = 1; + mov->mvhd.creation_time = time(NULL) + 0x7C25B080; // 1970 based -> 1904 based; + mov->mvhd.modification_time = mov->mvhd.creation_time; + mov->mvhd.timescale = 1000; + mov->mvhd.duration = 0; // placeholder + + mov_writer_init(mov); + mov_write_ftyp(mov); + + // free(reserved for 64bit mdat) + mov_buffer_w32(&mov->io, 8); /* size */ + mov_buffer_write(&mov->io, "free", 4); + + // mdat + writer->mdat_offset = mov_buffer_tell(&mov->io); + mov_buffer_w32(&mov->io, 0); /* size */ + mov_buffer_write(&mov->io, "mdat", 4); + return writer; +} + +static int mov_writer_move(struct mov_t* mov, uint64_t to, uint64_t from, size_t bytes); +void mov_writer_destroy(struct mov_writer_t* writer) +{ + int i; + uint64_t offset, offset2; + struct mov_t* mov; + struct mov_track_t* track; + mov = &writer->mov; + + // finish mdat box + if (writer->mdat_size + 8 <= UINT32_MAX) + { + mov_write_size(mov, writer->mdat_offset, (uint32_t)(writer->mdat_size + 8)); /* update size */ + } + else + { + offset2 = mov_buffer_tell(&mov->io); + writer->mdat_offset -= 8; // overwrite free box + mov_buffer_seek(&mov->io, writer->mdat_offset); + mov_buffer_w32(&mov->io, 1); + mov_buffer_write(&mov->io, "mdat", 4); + mov_buffer_w64(&mov->io, writer->mdat_size + 16); + mov_buffer_seek(&mov->io, offset2); + } + + // finish sample info + for (i = 0; i < mov->track_count; i++) + { + track = &mov->tracks[i]; + if(track->sample_count < 1) + continue; + + // pts in ms + track->mdhd.duration = (track->samples[track->sample_count - 1].dts - track->samples[0].dts); + if (track->sample_count > 1) + { + // duration += 3/4 * avg-duration + 1/4 * last-frame-duration + track->mdhd.duration += track->mdhd.duration * 3 / (track->sample_count - 1) / 4 + (track->samples[track->sample_count - 1].dts - track->samples[track->sample_count - 2].dts) / 4; + } + //track->mdhd.duration = track->mdhd.duration * track->mdhd.timescale / 1000; + track->tkhd.duration = track->mdhd.duration * mov->mvhd.timescale / track->mdhd.timescale; + if (track->tkhd.duration > mov->mvhd.duration) + mov->mvhd.duration = track->tkhd.duration; // maximum track duration + } + + // write moov box + offset = mov_buffer_tell(&mov->io); + mov_write_moov(mov); + offset2 = mov_buffer_tell(&mov->io); + + if (MOV_FLAG_FASTSTART & mov->flags) + { + // check stco -> co64 + uint64_t co64 = 0; + for (i = 0; i < mov->track_count; i++) + { + co64 += mov_stco_size(&mov->tracks[i], offset2 - offset); + } + + if (co64) + { + uint64_t sz; + do + { + sz = co64; + co64 = 0; + for (i = 0; i < mov->track_count; i++) + { + co64 += mov_stco_size(&mov->tracks[i], offset2 - offset + sz); + } + } while (sz != co64); + } + + // rewrite moov + for (i = 0; i < mov->track_count; i++) + mov->tracks[i].offset += (offset2 - offset) + co64; + + mov_buffer_seek(&mov->io, offset); + mov_write_moov(mov); + assert(mov_buffer_tell(&mov->io) == offset2 + co64); + offset2 = mov_buffer_tell(&mov->io); + + mov_writer_move(mov, writer->mdat_offset, offset, (size_t)(offset2 - offset)); + } + + mov_write_tail(mov); + for (i = 0; i < mov->track_count; i++) + mov_free_track(mov->tracks + i); + if (mov->tracks) + free(mov->tracks); + free(writer); +} + +static int mov_writer_move(struct mov_t* mov, uint64_t to, uint64_t from, size_t bytes) +{ + uint8_t* ptr; + uint64_t i, j; + void* buffer[2]; + + assert(bytes < INT32_MAX); + ptr = malloc((size_t)(bytes * 2)); + if (NULL == ptr) + return -ENOMEM; + buffer[0] = ptr; + buffer[1] = ptr + bytes; + + mov_buffer_seek(&mov->io, from); + mov_buffer_read(&mov->io, buffer[0], bytes); + mov_buffer_seek(&mov->io, to); + mov_buffer_read(&mov->io, buffer[1], bytes); + + j = 0; + for (i = to; i < from; i += bytes) + { + mov_buffer_seek(&mov->io, i); + mov_buffer_write(&mov->io, buffer[j], bytes); + // MSDN: fopen https://msdn.microsoft.com/en-us/library/yeby3zcb.aspx + // When the "r+", "w+", or "a+" access type is specified, both reading and + // writing are enabled (the file is said to be open for "update"). + // However, when you switch from reading to writing, the input operation + // must encounter an EOF marker. If there is no EOF, you must use an intervening + // call to a file positioning function. The file positioning functions are + // fsetpos, fseek, and rewind. + // When you switch from writing to reading, you must use an intervening + // call to either fflush or to a file positioning function. + mov_buffer_seek(&mov->io, i+bytes); + mov_buffer_read(&mov->io, buffer[j], bytes); + j ^= 1; + } + + mov_buffer_seek(&mov->io, i); + mov_buffer_write(&mov->io, buffer[j], bytes - (size_t)(i - from)); + + free(ptr); + return mov_buffer_error(&mov->io); +} + +int mov_writer_write(struct mov_writer_t* writer, int track, const void* data, size_t bytes, int64_t pts, int64_t dts, int flags) +{ + struct mov_t* mov; + struct mov_sample_t* sample; + + assert(bytes < UINT32_MAX); + if (track < 0 || track >= (int)writer->mov.track_count) + return -ENOENT; + + mov = &writer->mov; + mov->track = &mov->tracks[track]; + + if (mov->track->sample_count + 1 >= mov->track->sample_offset) + { + void* ptr = realloc(mov->track->samples, sizeof(struct mov_sample_t) * (mov->track->sample_offset + 1024)); + if (NULL == ptr) return -ENOMEM; + mov->track->samples = ptr; + mov->track->sample_offset += 1024; + } + + pts = pts * mov->track->mdhd.timescale / 1000; + dts = dts * mov->track->mdhd.timescale / 1000; + + sample = &mov->track->samples[mov->track->sample_count++]; + sample->sample_description_index = 1; + sample->bytes = (uint32_t)bytes; + sample->flags = flags; + sample->data = NULL; + sample->pts = pts; + sample->dts = dts; + + sample->offset = mov_buffer_tell(&mov->io); + mov_buffer_write(&mov->io, data, bytes); + + if (INT64_MIN == mov->track->start_dts) + mov->track->start_dts = sample->dts; + writer->mdat_size += bytes; // update media data size + return mov_buffer_error(&mov->io); +} + +int mov_writer_add_audio(struct mov_writer_t* writer, uint8_t object, int channel_count, int bits_per_sample, int sample_rate, const void* extra_data, size_t extra_data_size) +{ + struct mov_t* mov; + struct mov_track_t* track; + + mov = &writer->mov; + track = mov_add_track(mov); + if (NULL == track) + return -ENOMEM; + + if (0 != mov_add_audio(track, &mov->mvhd, 1000, object, channel_count, bits_per_sample, sample_rate, extra_data, extra_data_size)) + return -ENOMEM; + + mov->mvhd.next_track_ID++; + return mov->track_count++; +} + +int mov_writer_add_video(struct mov_writer_t* writer, uint8_t object, int width, int height, const void* extra_data, size_t extra_data_size) +{ + struct mov_t* mov; + struct mov_track_t* track; + + mov = &writer->mov; + track = mov_add_track(mov); + if (NULL == track) + return -ENOMEM; + + if (0 != mov_add_video(track, &mov->mvhd, 1000, object, width, height, extra_data, extra_data_size)) + return -ENOMEM; + + mov->mvhd.next_track_ID++; + return mov->track_count++; +} + +int mov_writer_add_subtitle(struct mov_writer_t* writer, uint8_t object, const void* extra_data, size_t extra_data_size) +{ + struct mov_t* mov; + struct mov_track_t* track; + uint32_t i; + + mov = &writer->mov; + track = mov_add_track(mov); + if (NULL == track) + return -ENOMEM; + + if (0 != mov_add_subtitle(track, &mov->mvhd, 1000, object, extra_data, extra_data_size)) + return -ENOMEM; + + for (i = 1; object == MOV_OBJECT_CHAPTER && i < mov->mvhd.next_track_ID; i++) + mov->tracks[i - 1].chpl_track = mov->mvhd.next_track_ID; + + mov->mvhd.next_track_ID++; + return mov->track_count++; +} + +int mov_writer_add_udta(mov_writer_t* mov, const void* data, size_t size) +{ + mov->mov.udta = data; + mov->mov.udta_size = size; + return 0; +} diff --git a/MediaServer/libmpeg/include/mpeg-element-descriptor.h b/MediaServer/libmpeg/include/mpeg-element-descriptor.h new file mode 100644 index 0000000..68bab1d --- /dev/null +++ b/MediaServer/libmpeg/include/mpeg-element-descriptor.h @@ -0,0 +1,289 @@ +#ifndef _mpeg_element_descriptor_h_ +#define _mpeg_element_descriptor_h_ + +#include "mpeg-util.h" + +#define SERVICE_ID 0x71 +#define SERVICE_NAME "ireader/media-server" + +int mpeg_elment_descriptor(struct mpeg_bits_t* reader); + +typedef struct _video_stream_descriptor_t +{ + uint32_t multiple_frame_rate_flag : 1; + // Table 2-47 - Frame rate code + // 23.976/24.0/25.0/29.97/30.0/50.0/59.94/60.0 + uint32_t frame_rate_code : 4; + uint32_t MPEG_1_only_flag : 1; + uint32_t constrained_parameter_flag : 1; + uint32_t still_picture_flag : 1; + + // MPEG_1_only_flag == 0 + uint32_t profile_and_level_indication : 8; + uint32_t chroma_format : 2; + uint32_t frame_rate_extension_flag : 1; +} video_stream_descriptor_t; + +int video_stream_descriptor(struct mpeg_bits_t* reader, uint8_t len); + +typedef struct _audio_stream_descriptor_t +{ + uint32_t free_format_flag : 1; + uint32_t ID : 1; + uint32_t layer : 2; + uint32_t variable_rate_audio_indicator : 1; +} audio_stream_descriptor_t; + +int audio_stream_descriptor(struct mpeg_bits_t* reader, uint8_t len); + +/* +Table 2-50 - Hierarchy_type field values +Value Description +0 Reserved +1 Spatial Scalability +2 SNR Scalability +3 Temporal Scalability +4 Data partitioning +5 Extension bitstream +6 Private Stream +7 Multi-view Profile +8 Combined Scalability +9 MVC video sub-bitstream +10-14 Reserved +15 Base layer or MVC base view sub-bitstream or AVC video sub-bitstream of MVC +*/ +typedef struct _hierarchy_descriptor_t +{ + uint32_t no_view_scalability_flag : 1; + uint32_t no_temporal_scalability_flag : 1; + uint32_t no_spatial_scalability_flag : 1; + uint32_t no_quality_scalability_flag : 1; + uint32_t hierarchy_type : 4; + uint32_t tref_present_flag : 1; + uint32_t reserved1 : 1; + uint32_t hierarchy_layer_index : 6; + uint32_t reserved2 : 2; + uint32_t hierarchy_embedded_layer_index : 6; + uint32_t reserved3 : 2; + uint32_t hierarchy_channel : 6; +} hierarchy_descriptor_t; + +int hierarchy_descriptor(struct mpeg_bits_t* reader, uint8_t len); + +int registration_descriptor(struct mpeg_bits_t* reader, uint8_t len); + +typedef struct _language_descriptor_t +{ + uint32_t code : 24; + uint32_t audio : 8; +} language_descriptor_t; + +int language_descriptor(struct mpeg_bits_t* reader, uint8_t len); + +typedef struct _system_clock_descriptor_t +{ + uint32_t external_clock_reference_indicator : 1; + uint32_t clock_accuracy_integer : 6; + uint32_t clock_accuracy_exponent : 3; +} system_clock_descriptor_t; + +int system_clock_descriptor(struct mpeg_bits_t* reader, uint8_t len); + +typedef struct _mpeg4_video_descriptor_t +{ + uint8_t visual_profile_and_level; +} mpeg4_video_descriptor_t; + +int mpeg4_video_descriptor(struct mpeg_bits_t* reader, uint8_t len); + +typedef struct _metadata_pointer_descriptor +{ + uint32_t metadata_application_format_identifier; + uint32_t metadata_format_identifier; + uint8_t metadata_service_id; + uint8_t metadata_locator_record_length; + uint8_t MPEG_carriage_flags; + uint16_t program_number; + uint16_t transport_stream_location; + uint16_t transport_stream_id; +} metadata_pointer_descriptor_t; + +int metadata_pointer_descriptor(struct mpeg_bits_t* reader, uint8_t len); + +typedef struct _metadata_descriptor_t +{ + uint32_t metadata_application_format_identifier; + uint32_t metadata_format_identifier; + uint8_t metadata_service_id; + uint8_t service_identification_length; + uint8_t decoder_config_flags; + uint8_t decoder_config_length; + uint8_t dec_config_identification_record_length; + uint8_t decoder_config_metadata_service_id; +} metadata_descriptor_t; + +int metadata_descriptor(struct mpeg_bits_t* reader, uint8_t len); + +typedef struct _mpeg4_audio_descriptor_t +{ + uint8_t profile_and_level; +} mpeg4_audio_descriptor_t; + +int mpeg4_audio_descriptor(struct mpeg_bits_t* reader, uint8_t len); + +typedef struct _avc_video_descriptor_t +{ + uint32_t profile_idc : 8; + uint32_t constraint_set0_flag : 1; + uint32_t constraint_set1_flag : 1; + uint32_t constraint_set2_flag : 1; + uint32_t constraint_set3_flag : 1; + uint32_t constraint_set4_flag : 1; + uint32_t constraint_set5_flag : 1; + uint32_t AVC_compatible_flags : 2; + uint32_t level_idc : 8; + uint32_t AVC_still_present : 1; + uint32_t AVC_24_hour_picture_flag : 1; + uint32_t frame_packing_SEI_not_present_flag : 1; +} avc_video_descriptor_t; + +int avc_video_descriptor(struct mpeg_bits_t* reader, uint8_t len); + +typedef struct _avc_timing_hrd_descriptor_t +{ + uint32_t hrd_management_valid_flag : 1; + uint32_t picture_and_timing_info_present : 1; + uint32_t _90kHZ_flag : 1; + uint32_t fixed_frame_rate_flag : 1; + uint32_t temporal_poc_flag : 1; + uint32_t picture_to_display_conversion_flag : 1; + uint32_t N; + uint32_t K; + uint32_t num_unit_in_tick; +} avc_timing_hrd_descriptor_t; + +int avc_timing_hrd_descriptor(struct mpeg_bits_t* reader, uint8_t len); + +typedef struct _mpeg2_aac_descriptor_t +{ + uint32_t profile : 8; + uint32_t channel_configuration : 8; + uint32_t additional_information : 8; +} mpeg2_aac_descriptor_t; + +int mpeg2_aac_descriptor(struct mpeg_bits_t* reader, uint8_t len); + +typedef struct _svc_extension_descriptor_t +{ + uint16_t width; + uint16_t height; + uint16_t frame_rate; + uint16_t average_bitrate; + uint16_t maximum_bitrate; + uint32_t quality_id_start : 4; + uint32_t quality_id_end : 4; + uint32_t temporal_id_start : 3; + uint32_t temporal_id_end : 3; + uint32_t dependency_id : 3; + uint32_t no_sei_nal_unit_present : 1; +} svc_extension_descriptor_t; + +int svc_extension_descriptor(struct mpeg_bits_t* reader, uint8_t len); + +typedef struct _mvc_extension_descriptor_t +{ + uint16_t average_bit_rate; + uint16_t maximum_bitrate; + uint32_t view_order_index_min : 10; + uint32_t view_order_index_max : 10; + uint32_t temporal_id_start : 3; + uint32_t temporal_id_end : 3; + uint32_t no_sei_nal_unit_present : 1; + uint32_t no_prefix_nal_unit_present : 1; +} mvc_extension_descriptor_t; + +int mvc_extension_descriptor(struct mpeg_bits_t* reader, uint8_t len); + +typedef struct _hevc_video_descriptor_t +{ + uint8_t profile_space : 2; + uint8_t tier_flag : 1; + uint8_t profile_idc : 5; + uint32_t profile_compatibility_indication; + + uint64_t progressive_source_flag : 1; + uint64_t interlaced_source_flag : 1; + uint64_t non_packed_constraint_flag : 1; + uint64_t frame_only_constraint_flag : 1; + uint64_t copied_44bits : 44; + uint64_t level_idc : 8; + uint64_t temporal_layer_subset_flag : 1; + uint64_t HEVC_still_present_flag : 1; + uint64_t HEVC_24hr_picture_present_flag : 1; + uint64_t sub_pic_hrd_params_not_present_flag : 1; + uint64_t reserved : 2; + uint64_t HDR_WCG_idc : 2; + + uint8_t temporal_id_min : 3; + uint8_t temporal_id_max : 3; +} hevc_video_descriptor_t; + +int hevc_video_descriptor(struct mpeg_bits_t* reader, uint8_t len); + +typedef struct _vvc_video_descriptor_t +{ + uint8_t profile_idc : 7; + uint8_t tier_flag : 1; + uint8_t num_sub_profiles; + uint32_t sub_profile_idc[32]; + + uint32_t progressive_source_flag : 1; + uint32_t interlaced_source_flag : 1; + uint32_t non_packed_constraint_flag : 1; + uint32_t frame_only_constraint_flag : 1; + uint32_t reserved_zero_4bits : 4; + uint32_t level_idc : 8; + uint32_t temporal_layer_subset_flag : 1; + uint32_t VVC_still_present_flag : 1; + uint32_t VVC_24hr_picture_present_flag : 1; + uint32_t reserved1 : 5; + uint32_t HDR_WCG_idc : 2; + uint32_t reserved2 : 2; + uint32_t video_properties_tag : 4; + + uint8_t temporal_id_min : 3; + uint8_t temporal_id_max : 3; +} vvc_video_descriptor_t; + +int vvc_video_descriptor(struct mpeg_bits_t* reader, uint8_t len); + +typedef struct _evc_video_descriptor_t +{ + uint8_t profile_idc; + uint8_t level_idc; + uint32_t toolset_idc_h; + uint32_t toolset_idc_l; + + uint32_t progressive_source_flag : 1; + uint32_t interlaced_source_flag : 1; + uint32_t non_packed_constraint_flag : 1; + uint32_t frame_only_constraint_flag : 1; + uint32_t reserved : 1; + uint32_t temporal_layer_subset_flag : 1; + uint32_t EVC_still_present_flag : 1; + uint32_t EVC_24hr_picture_present_flag : 1; + uint32_t HDR_WCG_idc : 2; + uint32_t reserved2 : 2; + uint32_t video_properties_tag : 4; + uint32_t temporal_id_min : 3; + uint32_t temporal_id_max : 3; +} evc_video_descriptor_t; + +int evc_video_descriptor(struct mpeg_bits_t* reader, uint8_t len); + +int clock_extension_descriptor(struct mpeg_bits_t* reader, uint8_t len); +size_t clock_extension_descriptor_write(uint8_t* data, size_t bytes, int64_t clock); + +size_t service_extension_descriptor_write(uint8_t* data, size_t bytes); + +#endif /* !_mpeg_element_descriptor_h_ */ diff --git a/MediaServer/libmpeg/include/mpeg-muxer.h b/MediaServer/libmpeg/include/mpeg-muxer.h new file mode 100644 index 0000000..620285c --- /dev/null +++ b/MediaServer/libmpeg/include/mpeg-muxer.h @@ -0,0 +1,63 @@ +#ifndef _mpeg_muxer_h_ +#define _mpeg_muxer_h_ + +#include +#include +#include "mpeg-ps.h" +#include "mpeg-ts.h" +#include "mpeg-ts-proto.h" + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct mpeg_muxer_t mpeg_muxer_t; +typedef struct ps_muxer_func_t mpeg_muxer_func_t; + +/// Create/Destroy MPEG2-TS/PS muxer +mpeg_muxer_t *mpeg_muxer_create(int is_ps, const mpeg_muxer_func_t *func, void *param); +int mpeg_muxer_destroy(mpeg_muxer_t *muxer); + +/// Add audio/video stream +/// @param[in] codecid PSI_STREAM_H264/PSI_STREAM_H265/PSI_STREAM_AAC, see more @mpeg-ts-proto.h +/// @param[in] extradata itu h.222.0 program and program element descriptors, NULL for H.264/H.265/AAC +/// @param[in] bytes extradata size in byte +/// @return <=0-error, >0-audio/video stream id +int mpeg_muxer_add_stream(mpeg_muxer_t *muxer, int codecid, const void *extradata, size_t extradata_size); + +/// input ES +/// @param[in] muxer object return by mpeg_muxer_create +/// @param[in] stream stream id, return by mpeg_muxer_add_stream +/// @param[in] flags 0x0001-video IDR frame, 0x8000-H.264/H.265 with AUD +/// @param[in] pts presentation time stamp(in 90KHZ) +/// @param[in] dts decoding time stamp(in 90KHZ) +/// @param[in] data ES memory +/// @param[in] bytes ES length in byte +/// @return 0-ok, ENOMEM-alloc failed, <0-error +int mpeg_muxer_input(mpeg_muxer_t *muxer, int stream, int flags, int64_t pts, int64_t dts, const void *data, size_t bytes); + +///////////////////// The following interfaces are only applicable to mpeg-ts /////////////////////////////// + +/// Reset PAT/PCR period +int mpeg_muxer_reset(mpeg_muxer_t *muxer); + +/// FOR MULTI-PROGRAM TS STREAM ONLY +/// Add a program +/// @param[in] pn program number, valid value: [1, 0xFFFF] +/// @return 0-OK, <0-error +int mpeg_muxer_add_program(mpeg_muxer_t *muxer, uint16_t pn, const void *info, int bytes); + +/// Remove a program by program number +/// @param[in] pn program number, valid value: [1, 0xFFFF] +/// @return 0-OK, <0-error +int mpeg_muxer_remove_program(mpeg_muxer_t *muxer, uint16_t pn); + +/// Add program stream(same as mpeg_ts_add_stream except program number) +/// @param[in] pn mpeg_ts_add_program program number +/// @return 0-OK, <0-error +int mpeg_muxer_add_program_stream(mpeg_muxer_t *muxer, uint16_t pn, int codecid, const void *extra_data, size_t extra_data_size); + +#ifdef __cplusplus +} +#endif +#endif /* !_mpeg_muxer_h_ */ diff --git a/MediaServer/libmpeg/include/mpeg-pes-proto.h b/MediaServer/libmpeg/include/mpeg-pes-proto.h new file mode 100644 index 0000000..8248b49 --- /dev/null +++ b/MediaServer/libmpeg/include/mpeg-pes-proto.h @@ -0,0 +1,6 @@ +#ifndef _mpeg_pes_proto_h_ +#define _mpeg_pes_proto_h_ + +#pragma message("This file is deprecated. Please use \"mpeg-ts.h\" or \"mpeg-ps.h\" only") + +#endif /* !_mpeg_pes_proto_h_ */ diff --git a/MediaServer/libmpeg/include/mpeg-proto.h b/MediaServer/libmpeg/include/mpeg-proto.h new file mode 100644 index 0000000..0ad4978 --- /dev/null +++ b/MediaServer/libmpeg/include/mpeg-proto.h @@ -0,0 +1,156 @@ +#ifndef _mpeg_proto_h_ +#define _mpeg_proto_h_ + +// Table 2-3 - PID table(p36) +enum ETS_PID +{ + TS_PID_PAT = 0x00, // program association table + TS_PID_CAT = 0x01, // conditional access table + TS_PID_TSDT = 0x02, // transport stream description table + TS_PID_IPMP = 0x03, // IPMP control information table + // 0x0004-0x000F Reserved + // 0x0010-0x1FFE May be assigned as network_PID, Program_map_PID, elementary_PID, or for other purposes + TS_PID_SDT = 0x11, // https://en.wikipedia.org/wiki/Service_Description_Table / https://en.wikipedia.org/wiki/MPEG_transport_stream + TS_PID_USER = 0x0042, + TS_PID_NULL = 0x1FFF, // Null packet +}; + +// 2.4.4.4 Table_id assignments +// Table 2-31 - table_id assignment values(p66/p39) +enum EPAT_TID +{ + PAT_TID_PAS = 0x00, // program_association_section + PAT_TID_CAS = 0x01, // conditional_access_section(CA_section) + PAT_TID_PMS = 0x02, // TS_program_map_section + PAT_TID_SDS = 0x03, // TS_description_section + PAT_TID_MPEG4_scene = 0x04, // ISO_IEC_14496_scene_description_section + PAT_TID_MPEG4_object = 0x05, // ISO_IEC_14496_object_descriptor_section + PAT_TID_META = 0x06, // Metadata_section + PAT_TID_IPMP = 0x07, // IPMP_Control_Information_section(defined in ISO/IEC 13818-11) + PAT_TID_H222 = 0x08, // Rec. ITU-T H.222.0 | ISO/IEC 13818-1 reserved + PAT_TID_USER = 0x40, // User private + PAT_TID_SDT = 0x42, // service_description_section + PAT_TID_Forbidden = 0xFF, +}; + +// ISO/IEC 13818-1:2015 (E) +// 2.4.4.9 Semantic definition of fields in transport stream program map section +// Table 2-34 - Stream type assignments(p69) +enum EPSI_STREAM_TYPE +{ + PSI_STREAM_RESERVED = 0x00, // ITU-T | ISO/IEC Reserved + PSI_STREAM_MPEG1 = 0x01, // ISO/IEC 11172-2 Video + PSI_STREAM_MPEG2 = 0x02, // Rec. ITU-T H.262 | ISO/IEC 13818-2 Video or ISO/IEC 11172-2 constrained parameter video stream(see Note 2) + PSI_STREAM_AUDIO_MPEG1 = 0x03, // ISO/IEC 11172-3 Audio + PSI_STREAM_MP3 = 0x04, // ISO/IEC 13818-3 Audio + PSI_STREAM_PRIVATE_SECTION = 0x05, // Rec. ITU-T H.222.0 | ISO/IEC 13818-1 private_sections + PSI_STREAM_PRIVATE_DATA = 0x06, // Rec. ITU-T H.222.0 | ISO/IEC 13818-1 PES packets containing private data + PSI_STREAM_MHEG = 0x07, // ISO/IEC 13522 MHEG + PSI_STREAM_DSMCC = 0x08, // Rec. ITU-T H.222.0 | ISO/IEC 13818-1 Annex A DSM-CC + PSI_STREAM_H222_ATM = 0x09, // Rec. ITU-T H.222.1 + PSI_STREAM_DSMCC_A = 0x0a, // ISO/IEC 13818-6(Extensions for DSM-CC) type A + PSI_STREAM_DSMCC_B = 0x0b, // ISO/IEC 13818-6(Extensions for DSM-CC) type B + PSI_STREAM_DSMCC_C = 0x0c, // ISO/IEC 13818-6(Extensions for DSM-CC) type C + PSI_STREAM_DSMCC_D = 0x0d, // ISO/IEC 13818-6(Extensions for DSM-CC) type D + PSI_STREAM_H222_Aux = 0x0e, // Rec. ITU-T H.222.0 | ISO/IEC 13818-1 auxiliary + PSI_STREAM_AAC = 0x0f, // ISO/IEC 13818-7 Audio with ADTS transport syntax + PSI_STREAM_MPEG4 = 0x10, // ISO/IEC 14496-2 Visual + PSI_STREAM_MPEG4_AAC_LATM = 0x11, // ISO/IEC 14496-3 Audio with the LATM transport syntax as defined in ISO/IEC 14496-3 + PSI_STREAM_MPEG4_PES = 0x12, // ISO/IEC 14496-1 SL-packetized stream or FlexMux stream carried in PES packets + PSI_STREAM_MPEG4_SECTIONS = 0x13, // ISO/IEC 14496-1 SL-packetized stream or FlexMux stream carried in ISO/IEC 14496_sections + PSI_STREAM_MPEG2_SDP = 0x14, // ISO/IEC 13818-6 Synchronized Download Protocol + PSI_STREAM_PES_META = 0x15, // Metadata carried in PES packets + PSI_STREAM_SECTION_META = 0x16, // Metadata carried in metadata_sections + PSI_STREAM_DSMCC_DATA = 0x17, // Metadata carried in ISO/IEC 13818-6 Data Carousel + PSI_STREAM_DSMCC_OBJECT = 0x18, // Metadata carried in ISO/IEC 13818-6 Object Carousel + PSI_STREAM_DSMCC_SDP = 0x19, // Metadata carried in ISO/IEC 13818-6 Synchronized Download Protocol + PSI_STREAM_MPEG2_IPMP = 0x1a, // IPMP stream (defined in ISO/IEC 13818-11, MPEG-2 IPMP) + PSI_STREAM_H264 = 0x1b, // H.264 + PSI_STREAM_MPEG4_AAC = 0x1c, // ISO/IEC 14496-3 Audio, without using any additional transport syntax, such as DST, ALS and SLS + PSI_STREAM_MPEG4_TEXT = 0x1d, // ISO/IEC 14496-17 Text + PSI_STREAM_AUX_VIDEO = 0x1e, // Auxiliary video stream as defined in ISO/IEC 23002-3 + PSI_STREAM_H264_SVC = 0x1f, // SVC video sub-bitstream of an AVC video stream conforming to one or more profiles defined in Annex G of Rec. ITU-T H.264 | ISO/IEC 14496-10 + PSI_STREAM_H264_MVC = 0x20, // MVC video sub-bitstream of an AVC video stream conforming to one or more profiles defined in Annex H of Rec. ITU-T H.264 | ISO/IEC 14496-10 + PSI_STREAM_JPEG_2000 = 0x21, // Video stream conforming to one or more profiles as defined in Rec. ITU-T T.800 | ISO/IEC 15444-1 + PSI_STREAM_MPEG2_3D = 0x22, // Additional view Rec. ITU-T H.262 | ISO/IEC 13818-2 video stream for service-compatible stereoscopic 3D services + PSI_STREAM_MPEG4_3D = 0x23, // Additional view Rec. ITU-T H.264 | ISO/IEC 14496-10 video stream conforming to one or more profiles defined in Annex A for service-compatible stereoscopic 3D services + PSI_STREAM_H265 = 0x24, // Rec. ITU-T H.265 | ISO/IEC 23008-2 video stream or an HEVC temporal video sub-bitstream + PSI_STREAM_H265_subset = 0x25, // HEVC temporal video subset of an HEVC video stream conforming to one or more profiles defined in Annex A of Rec. ITU-T H.265 | ISO/IEC 23008-2 + PSI_STREAM_H264_MVCD = 0x26, // MVCD video sub-bitstream of an AVC video stream conforming to one or more profiles defined in Annex I of Rec. ITU-T H.264 | ISO/IEC 14496-10 + PSI_STREAM_JPEG_XS = 0x32, // JPEG XS video stream conforming to one or more profiles as defined in ISO/IEC 21122-2 + PSI_STREAM_H266 = 0x33, // VVC video stream or a VVC temporal video sub-bitstream conforming to one or more profiles defined in Annex A of Rec. ITU-T H.266 | ISO/IEC 23090-3 + PSI_STREAM_H266_subset = 0x34, // VVC temporal video subset of a VVC video stream conforming to one or more profiles defined in Annex A of Rec. ITU-T H.266 | ISO/IEC 23090-3 + PSI_STREAM_EVC = 0x35, // EVC video stream or an EVC temporal video sub-bitstream conforming to one or more profiles defined in ISO/IEC 23094-1 + PSI_STREAM_VP8 = 0x9d, + PSI_STREAM_VP9 = 0x9e, + PSI_STREAM_AV1 = 0x9f, // https://aomediacodec.github.io/av1-mpeg2-ts/ + // 0x27-0x7E Rec. ITU-T H.222.0 | ISO/IEC 13818-1 Reserved + PSI_STREAM_IPMP = 0x7F, // IPMP stream + // 0x80-0xFF User Private + PSI_STREAM_VIDEO_CAVS = 0x42, // ffmpeg/libavformat/mpegts.h + PSI_STREAM_AUDIO_AC3 = 0x81, // ffmpeg/libavformat/mpegts.h + PSI_STREAM_AUDIO_EAC3 = 0x87, // ffmpeg/libavformat/mpegts.h + PSI_STREAM_AUDIO_DTS = 0x8a, // ffmpeg/libavformat/mpegts.h + PSI_STREAM_VIDEO_DIRAC = 0xd1, // ffmpeg/libavformat/mpegts.h + PSI_STREAM_VIDEO_AVS3 = 0xd4, // ffmpeg/libavformat/mpegts.h + PSI_STREAM_VIDEO_VC1 = 0xea, // ffmpeg/libavformat/mpegts.h + PSI_STREAM_VIDEO_SVAC = 0x80, // GBT 25724-2010 SVAC(2014) + PSI_STREAM_AUDIO_SVAC = 0x9B, // GBT 25724-2010 SVAC(2014) + PSI_STREAM_AUDIO_G711A = 0x90, // GBT 25724-2010 SVAC(2014) + PSI_STREAM_AUDIO_G711U = 0x91, + PSI_STREAM_AUDIO_G722 = 0x92, + PSI_STREAM_AUDIO_G723 = 0x93, + PSI_STREAM_AUDIO_G729 = 0x99, + PSI_STREAM_AUDIO_OPUS = 0x9c, // https://opus-codec.org/docs/ETSI_TS_opus-v0.1.3-draft.pdf +}; + +// ISO/IEC 13818-1:2015 (E) +// 2.4.3.7 Semantic definition of fields in PES packet +// Table 2-22 - Stream_id assignments(p54) +// In transport streams, the stream_id may be set to any valid value which correctly describes the elementary stream type as defined in Table 2-22. +// In transport streams, the elementary stream type is specified in the program-specific information as specified in 2.4.4 +enum EPES_STREAM_ID +{ + PES_SID_SUB = 0x20, // ffmpeg/libavformat/mpeg.h + PES_SID_AC3 = 0x80, // ffmpeg/libavformat/mpeg.h + PES_SID_DTS = 0x88, // ffmpeg/libavformat/mpeg.h + PES_SID_LPCM = 0xA0, // ffmpeg/libavformat/mpeg.h + + PES_SID_EXTENSION = 0xB7, // PS system_header extension(p81) + PES_SID_END = 0xB9, // MPEG_program_end_code + PES_SID_START = 0xBA, // Pack start code + PES_SID_SYS = 0xBB, // System header start code + + PES_SID_PSM = 0xBC, // program_stream_map + PES_SID_PRIVATE_1 = 0xBD, // private_stream_1 + PES_SID_PADDING = 0xBE, // padding_stream + PES_SID_PRIVATE_2 = 0xBF, // private_stream_2 + PES_SID_AUDIO = 0xC0, // ISO/IEC 13818-3/11172-3/13818-7/14496-3 audio stream '110x xxxx' + PES_SID_VIDEO = 0xE0, // H.262 | H.264 | H.265 | ISO/IEC 13818-2/11172-2/14496-2/14496-10 video stream '1110 xxxx' + PES_SID_ECM = 0xF0, // ECM_stream + PES_SID_EMM = 0xF1, // EMM_stream + PES_SID_DSMCC = 0xF2, // H.222.0 | ISO/IEC 13818-1/13818-6_DSMCC_stream + PES_SID_13522 = 0xF3, // ISO/IEC_13522_stream + PES_SID_H222_A = 0xF4, // Rec. ITU-T H.222.1 type A + PES_SID_H222_B = 0xF5, // Rec. ITU-T H.222.1 type B + PES_SID_H222_C = 0xF6, // Rec. ITU-T H.222.1 type C + PES_SID_H222_D = 0xF7, // Rec. ITU-T H.222.1 type D + PES_SID_H222_E = 0xF8, // Rec. ITU-T H.222.1 type E + PES_SID_ANCILLARY = 0xF9, // ancillary_stream + PES_SID_MPEG4_SL = 0xFA, // ISO/IEC 14496-1_SL_packetized_stream + PES_SID_MPEG4_Flex = 0xFB, // ISO/IEC 14496-1_FlexMux_stream + PES_SID_META = 0xFC, // metadata stream + PES_SID_EXTEND = 0xFD, // extended_stream_id + PES_SID_RESERVED = 0xFE, // reserved data stream + PES_SID_PSD = 0xFF, // program_stream_directory +}; + +enum +{ + MPEG_FLAG_IDR_FRAME = 0x0001, + MPEG_FLAG_PACKET_LOST = 0x1000, // packet(s) lost before the packet(this packet is ok, but previous packet has missed or corrupted) + MPEG_FLAG_PACKET_CORRUPT = 0x2000, // this packet miss same data(packet lost) + MPEG_FLAG_H264_H265_WITH_AUD = 0x8000, +}; + +#endif /* !_mpeg_proto_h_ */ diff --git a/MediaServer/libmpeg/include/mpeg-ps-proto.h b/MediaServer/libmpeg/include/mpeg-ps-proto.h new file mode 100644 index 0000000..c25dda9 --- /dev/null +++ b/MediaServer/libmpeg/include/mpeg-ps-proto.h @@ -0,0 +1,6 @@ +#ifndef _mpeg_ps_proto_h_ +#define _mpeg_ps_proto_h_ + +#pragma message("This file is deprecated. Please use \"mpeg-ts.h\" or \"mpeg-ps.h\" only") + +#endif /* !_mpeg_ps_proto_h_ */ diff --git a/MediaServer/libmpeg/include/mpeg-ps.h b/MediaServer/libmpeg/include/mpeg-ps.h new file mode 100644 index 0000000..f03c024 --- /dev/null +++ b/MediaServer/libmpeg/include/mpeg-ps.h @@ -0,0 +1,112 @@ +#ifndef _mpeg_ps_h_ +#define _mpeg_ps_h_ + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +#include "mpeg-proto.h" + +enum +{ + STREAM_VIDEO_MPEG4 = PSI_STREAM_MPEG4, + STREAM_VIDEO_H264 = PSI_STREAM_H264, + STREAM_VIDEO_H265 = PSI_STREAM_H265, + STREAM_VIDEO_SVAC = PSI_STREAM_VIDEO_SVAC, + STREAM_VIDEO_AVS3 = PSI_STREAM_VIDEO_AVS3, + STREAM_AUDIO_MP3 = PSI_STREAM_AUDIO_MPEG1, + STREAM_AUDIO_AAC = PSI_STREAM_AAC, + STREAM_AUDIO_EAC3 = PSI_STREAM_AUDIO_EAC3, + STREAM_AUDIO_G711A = PSI_STREAM_AUDIO_G711A, + STREAM_AUDIO_G711U = PSI_STREAM_AUDIO_G711U, + STREAM_AUDIO_G722 = PSI_STREAM_AUDIO_G722, + STREAM_AUDIO_G723 = PSI_STREAM_AUDIO_G723, + STREAM_AUDIO_G729 = PSI_STREAM_AUDIO_G729, + STREAM_AUDIO_SVAC = PSI_STREAM_AUDIO_SVAC, + STREAM_AUDIO_OPUS = PSI_STREAM_AUDIO_OPUS, +}; + +// e.g.: deprecated STREAM_VIDEO_H264, please use with PSI_STREAM_H264 +//#pragma deprecated(STREAM_VIDEO_MPEG4,STREAM_VIDEO_H264,STREAM_VIDEO_H265,STREAM_VIDEO_SVAC,STREAM_AUDIO_MP3,STREAM_AUDIO_AAC,STREAM_AUDIO_EAC3,STREAM_AUDIO_G711A,STREAM_AUDIO_G711U,STREAM_AUDIO_G722,STREAM_AUDIO_G723,STREAM_AUDIO_G729,STREAM_AUDIO_SVAC,STREAM_AUDIO_OPUS) + +struct ps_muxer_func_t +{ + /// alloc new packet + /// @param[in] param user-defined parameter(by ps_muxer_create) + /// @param[in] bytes alloc memory size in byte + /// @return memory pointer + void* (*alloc)(void* param, size_t bytes); + + /// free packet + /// @param[in] param user-defined parameter(by ps_muxer_create) + /// @param[in] packet PS packet pointer(alloc return pointer) + void (*free)(void* param, void* packet); + + /// callback on PS packet done + /// @param[in] param user-defined parameter(by ps_muxer_create) + /// @param[in] stream stream id, return by ps_muxer_add_stream + /// @param[in] packet PS packet pointer(alloc return pointer) + /// @param[in] bytes packet size + /// @return 0-ok, other-error + int (*write)(void* param, int stream, void* packet, size_t bytes); +}; + +struct ps_muxer_t; +struct ps_muxer_t* ps_muxer_create(const struct ps_muxer_func_t *func, void* param); +int ps_muxer_destroy(struct ps_muxer_t* muxer); +/// Add audio/video stream +/// @param[in] codecid PSI_STREAM_H264/PSI_STREAM_H265/PSI_STREAM_H266/PSI_STREAM_AAC, see more @mpeg-ts-proto.h +/// @param[in] extradata itu h.222.0 program and program element descriptors, NULL for H.264/H.265/AAC +/// @param[in] bytes extradata size in byte +/// @return <=0-error, >0-audio/video stream id +int ps_muxer_add_stream(struct ps_muxer_t* muxer, int codecid, const void* extradata, size_t bytes); + +/// input ES +/// @param[in] muxer MPEG-2 Program Stream packer(ps_muxer_create) +/// @param[in] stream stream id, return by ps_muxer_add_stream +/// @param[in] flags 0x0001-video IDR frame, 0x8000-H.264/H.265 with AUD +/// @param[in] pts presentation time stamp(in 90KHZ) +/// @param[in] dts decoding time stamp(in 90KHZ) +/// @param[in] data ES memory +/// @param[in] bytes ES length in byte +/// @return 0-ok, ENOMEM-alloc failed, <0-error +int ps_muxer_input(struct ps_muxer_t* muxer, int stream, int flags, int64_t pts, int64_t dts, const void* data, size_t bytes); + + +/// @param[in] codecid 0-unknown, other-enum EPSI_STREAM_TYPE, see more @mpeg-ts-proto.h +/// @return 0-ok, other-error +typedef int (*ps_demuxer_onpacket)(void* param, int stream, int codecid, int flags, int64_t pts, int64_t dts, const void* data, size_t bytes); + +struct ps_demuxer_t; +struct ps_demuxer_t* ps_demuxer_create(ps_demuxer_onpacket onpacket, void* param); +int ps_demuxer_destroy(struct ps_demuxer_t* demuxer); + +/// ps_demuxer_input return consumed bytes, the remain data MUST save and merge with next packet +/// int n = ps_demuxer_input(demuxer, data, bytes); +/// if(n >= 0 && n < bytes) +/// memcpy(NEXTBUFFER, data + n, bytes - n); +/// +/// @return >=0-consume bytes, <0-error +int ps_demuxer_input(struct ps_demuxer_t* demuxer, const uint8_t* data, size_t bytes); + +struct ps_demuxer_notify_t +{ + /// @param[in] param ps_demuxer_set_notify param + /// @param[in] stream ps stream id + /// @param[in] codecid ps codecid, e.g. STREAM_VIDEO_H264 + /// @param[in] extra stream extra data + /// @param[in] bytes extra data length + /// @param[in] finish 0-have more stream, 1-no more streams + void (*onstream)(void* param, int stream, int codecid, const void* extra, int bytes, int finish); +}; + +/// Set ps notify on PSM change +void ps_demuxer_set_notify(struct ps_demuxer_t* demuxer, struct ps_demuxer_notify_t* notify, void* param); + +#ifdef __cplusplus +} +#endif +#endif /* !_mpeg_ps_h_ */ diff --git a/MediaServer/libmpeg/include/mpeg-ts-proto.h b/MediaServer/libmpeg/include/mpeg-ts-proto.h new file mode 100644 index 0000000..25fb003 --- /dev/null +++ b/MediaServer/libmpeg/include/mpeg-ts-proto.h @@ -0,0 +1,6 @@ +#ifndef _mpeg_ts_proto_h_ +#define _mpeg_ts_proto_h_ + +#pragma message("This file is deprecated. Please use \"mpeg-ts.h\" or \"mpeg-ps.h\" only") + +#endif /* !_mpeg_ts_proto_h_ */ diff --git a/MediaServer/libmpeg/include/mpeg-ts.h b/MediaServer/libmpeg/include/mpeg-ts.h new file mode 100644 index 0000000..7406756 --- /dev/null +++ b/MediaServer/libmpeg/include/mpeg-ts.h @@ -0,0 +1,102 @@ +#ifndef _mpeg_ts_h_ +#define _mpeg_ts_h_ + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +#include "mpeg-proto.h" + +struct mpeg_ts_func_t +{ + /// alloc new packet + /// @param[in] param use-defined parameter(by mpeg_ps_create) + /// @param[in] bytes alloc memory size in byte(default 188) + /// @return memory pointer + void* (*alloc)(void* param, size_t bytes); + + /// free packet + /// @param[in] param use-defined parameter(by mpeg_ps_create) + /// @param[in] packet PS packet pointer(alloc return pointer) + void (*free)(void* param, void* packet); + + /// callback on PS packet done + /// @param[in] param use-defined parameter(by mpeg_ps_create) + /// @param[in] packet PS packet pointer(alloc return pointer) + /// @param[in] bytes packet size + /// @return 0-ok, other-error + int (*write)(void* param, const void* packet, size_t bytes); +}; + +/// Create/Destroy MPEG2-TS muxer +void* mpeg_ts_create(const struct mpeg_ts_func_t *func, void* param); +int mpeg_ts_destroy(void* ts); + +/// Add audio/video stream +/// @param[in] codecid PSI_STREAM_H264/PSI_STREAM_H265/PSI_STREAM_H266/PSI_STREAM_AAC, see more @mpeg-ts-proto.h +/// @param[in] extradata itu h.222.0 program and program element descriptors, NULL for H.264/H.265/AAC +/// @param[in] extradata_size extradata size in byte +/// @return <=0-error, >0-audio/video stream id +int mpeg_ts_add_stream(void* ts, int codecid, const void* extradata, size_t extradata_size); + +/// Muxer audio/video stream data +/// @param[in] stream stream id by mpeg_ts_add_stream +/// @param[in] flags 0x0001-video IDR frame, 0x8000-H.264/H.265 with AUD +/// @param[in] pts audio/video stream timestamp in 90*ms +/// @param[in] dts audio/video stream timestamp in 90*ms +/// @param[in] data H.264/H.265-AnnexB stream(include 00 00 00 01), AAC-ADTS stream +/// @return 0-ok, other-error +int mpeg_ts_write(void* ts, int stream, int flags, int64_t pts, int64_t dts, const void* data, size_t bytes); + +/// Reset PAT/PCR period +int mpeg_ts_reset(void* ts); + + +/// FOR MULTI-PROGRAM TS STREAM ONLY +/// Add a program +/// @param[in] pn program number, valid value: [1, 0xFFFF] +/// @return 0-OK, <0-error +int mpeg_ts_add_program(void* ts, uint16_t pn, const void* info, int bytes); + +/// Remove a program by program number +/// @param[in] pn program number, valid value: [1, 0xFFFF] +/// @return 0-OK, <0-error +int mpeg_ts_remove_program(void* ts, uint16_t pn); + +/// Add program stream(same as mpeg_ts_add_stream except program number) +/// @param[in] pn mpeg_ts_add_program program number +/// @return 0-OK, <0-error +int mpeg_ts_add_program_stream(void* ts, uint16_t pn, int codecid, const void* extra_data, size_t extra_data_size); + + +/// see more mpeg_ts_write +typedef int (*ts_demuxer_onpacket)(void* param, int program, int stream, int codecid, int flags, int64_t pts, int64_t dts, const void* data, size_t bytes); + +struct ts_demuxer_t; +struct ts_demuxer_t* ts_demuxer_create(ts_demuxer_onpacket onpacket, void* param); +int ts_demuxer_destroy(struct ts_demuxer_t* demuxer); +int ts_demuxer_input(struct ts_demuxer_t* demuxer, const uint8_t* data, size_t bytes); +int ts_demuxer_flush(struct ts_demuxer_t* demuxer); +int ts_demuxer_getservice(struct ts_demuxer_t* demuxer, int program, char* provider, int nprovider, char* name, int nname); + +struct ts_demuxer_notify_t +{ + /// @param[in] param ps_demuxer_set_notify param + /// @param[in] stream ps stream id + /// @param[in] codecid ps codecid, e.g. STREAM_VIDEO_H264 + /// @param[in] extra stream extra data + /// @param[in] bytes extra data length + /// @param[in] finish 0-have more stream, 1-no more streams + void (*onstream)(void* param, int stream, int codecid, const void* extra, int bytes, int finish); +}; + +/// Set ts notify on PMT change +void ts_demuxer_set_notify(struct ts_demuxer_t* demuxer, struct ts_demuxer_notify_t* notify, void* param); + +#ifdef __cplusplus +} +#endif +#endif /* !_mpeg_ts_h_ */ diff --git a/MediaServer/libmpeg/include/mpeg-types.h b/MediaServer/libmpeg/include/mpeg-types.h new file mode 100644 index 0000000..68f8219 --- /dev/null +++ b/MediaServer/libmpeg/include/mpeg-types.h @@ -0,0 +1,12 @@ +#ifndef _mpeg_types_h_ +#define _mpeg_types_h_ + +#include +#include +#include + +#define PTS_NO_VALUE INT64_MIN //(int64_t)0x8000000000000000L + +enum { MPEG_VCL_NONE = 0, MPEG_VCL_IDR, MPEG_VCL_P, MPEG_VCL_CORRUPT }; + +#endif /* !_mpeg_types_h_ */ diff --git a/MediaServer/libmpeg/include/mpeg-util.h b/MediaServer/libmpeg/include/mpeg-util.h new file mode 100644 index 0000000..45e0482 --- /dev/null +++ b/MediaServer/libmpeg/include/mpeg-util.h @@ -0,0 +1,172 @@ +#ifndef _mpeg_util_h_ +#define _mpeg_util_h_ + +#include "mpeg-types.h" +#include + +struct mpeg_bits_t +{ + struct + { + const uint8_t* ptr; + size_t len; + } data[2]; + + size_t count; // data count + size_t len; // total length + size_t off; // total offset + int err; // flags +}; + +static inline void mpeg_bits_init(struct mpeg_bits_t* bits, const void* data, size_t bytes) +{ + bits->data[0].ptr = (const uint8_t*)data; + bits->data[0].len = bytes; + bits->count = 1; + bits->len = bytes; + bits->off = 0; + bits->err = 0; +} + +static inline void mpeg_bits_init2(struct mpeg_bits_t* bits, const void* data1, size_t bytes1, const void* data2, size_t bytes2) +{ + bits->data[0].ptr = (const uint8_t*)data1; + bits->data[0].len = bytes1; + bits->data[1].ptr = (const uint8_t*)data2; + bits->data[1].len = bytes2; + bits->len = bytes1 + bytes2; + bits->count = 2; + bits->off = 0; + bits->err = 0; +} + +/// @return 0-ok, other-error +static inline int mpeg_bits_error(struct mpeg_bits_t* bits) +{ + return bits->err; +} + +static inline size_t mpeg_bits_length(struct mpeg_bits_t* bits) +{ + return bits->len; +} + +static inline size_t mpeg_bits_tell(struct mpeg_bits_t* bits) +{ + return bits->off; +} + +static inline void mpeg_bits_seek(struct mpeg_bits_t* bits, size_t n) +{ + bits->off = n; + if (n > bits->len) + bits->err = 1; +} + +static inline void mpeg_bits_skip(struct mpeg_bits_t* bits, size_t n) +{ + bits->off += n; + if (bits->off > bits->len) + bits->err = 1; +} + +static inline uint8_t mpeg_bits_read8(struct mpeg_bits_t* bits) +{ + size_t i, at; + + at = bits->off; + for (i = 0; 0 == bits->err && i < bits->count; i++) + { + if (at < bits->data[i].len) + { + bits->off++; + return bits->data[i].ptr[at]; + } + at -= bits->data[i].len; + } + + bits->err = 1; + return 0; +} + +static inline uint16_t mpeg_bits_read16(struct mpeg_bits_t* bits) +{ + uint16_t v; + v = (uint16_t)mpeg_bits_read8(bits) << 8; + v |= mpeg_bits_read8(bits); + return v; +} + +static inline uint32_t mpeg_bits_read32(struct mpeg_bits_t* bits) +{ + uint32_t v; + v = ((uint32_t)mpeg_bits_read16(bits)) << 16; + v |= mpeg_bits_read16(bits); + return v; +} + +static inline uint64_t mpeg_bits_read64(struct mpeg_bits_t* bits) +{ + uint64_t v; + v = ((uint64_t)mpeg_bits_read32(bits)) << 32; + v |= mpeg_bits_read32(bits); + return v; +} + +static inline uint64_t mpeg_bits_readn(struct mpeg_bits_t* bits, size_t n) +{ + size_t i; + uint64_t v; + + for (v = i = 0; i < n && i < 8; i++) + v = (v << 8) | mpeg_bits_read8(bits); + return v; +} + +static inline uint64_t mpeg_bits_tryread(struct mpeg_bits_t* bits, size_t n) +{ + uint64_t v; + size_t i, offset; + + if (bits->err || bits->off + n > bits->len) + return 0; + + offset = mpeg_bits_tell(bits); + for (v = i = 0; i < n && i < 8; i++) + v = (v << 8) | mpeg_bits_read8(bits); + mpeg_bits_seek(bits, offset); + + return v; +} + +static inline void nbo_w16(uint8_t* ptr, uint16_t val) +{ + ptr[0] = (uint8_t)((val >> 8) & 0xFF); + ptr[1] = (uint8_t)(val & 0xFF); +} + +static inline void nbo_w32(uint8_t* ptr, uint32_t val) +{ + ptr[0] = (uint8_t)((val >> 24) & 0xFF); + ptr[1] = (uint8_t)((val >> 16) & 0xFF); + ptr[2] = (uint8_t)((val >> 8) & 0xFF); + ptr[3] = (uint8_t)(val & 0xFF); +} + +void pcr_write(uint8_t *ptr, int64_t pcr); +int mpeg_stream_type_audio(int codecid); +int mpeg_stream_type_video(int codecid); + +int mpeg_h264_find_nalu(const uint8_t* p, size_t bytes, size_t* leading); +int mpeg_h264_find_new_access_unit(const uint8_t* data, size_t bytes, int* vcl); +int mpeg_h265_find_new_access_unit(const uint8_t* data, size_t bytes, int* vcl); +int mpeg_h266_find_new_access_unit(const uint8_t* data, size_t bytes, int* vcl); +int mpeg_h26x_verify(const uint8_t* data, size_t bytes, int* codec); + +int mpeg_h264_start_with_access_unit_delimiter(const uint8_t* p, size_t bytes); +int mpeg_h265_start_with_access_unit_delimiter(const uint8_t* p, size_t bytes); +int mpeg_h266_start_with_access_unit_delimiter(const uint8_t* p, size_t bytes); + +uint32_t mpeg_crc32(uint32_t crc, const uint8_t *buffer, uint32_t size); + +#endif /* !_mpeg_util_h_ */ diff --git a/MediaServer/libmpeg/source/mpeg-crc32.c b/MediaServer/libmpeg/source/mpeg-crc32.c new file mode 100644 index 0000000..740ceca --- /dev/null +++ b/MediaServer/libmpeg/source/mpeg-crc32.c @@ -0,0 +1,57 @@ +#include "mpeg-util.h" + +static uint32_t crc32table[256] = { + 0x00000000, 0xB71DC104, 0x6E3B8209, 0xD926430D, 0xDC760413, 0x6B6BC517, + 0xB24D861A, 0x0550471E, 0xB8ED0826, 0x0FF0C922, 0xD6D68A2F, 0x61CB4B2B, + 0x649B0C35, 0xD386CD31, 0x0AA08E3C, 0xBDBD4F38, 0x70DB114C, 0xC7C6D048, + 0x1EE09345, 0xA9FD5241, 0xACAD155F, 0x1BB0D45B, 0xC2969756, 0x758B5652, + 0xC836196A, 0x7F2BD86E, 0xA60D9B63, 0x11105A67, 0x14401D79, 0xA35DDC7D, + 0x7A7B9F70, 0xCD665E74, 0xE0B62398, 0x57ABE29C, 0x8E8DA191, 0x39906095, + 0x3CC0278B, 0x8BDDE68F, 0x52FBA582, 0xE5E66486, 0x585B2BBE, 0xEF46EABA, + 0x3660A9B7, 0x817D68B3, 0x842D2FAD, 0x3330EEA9, 0xEA16ADA4, 0x5D0B6CA0, + 0x906D32D4, 0x2770F3D0, 0xFE56B0DD, 0x494B71D9, 0x4C1B36C7, 0xFB06F7C3, + 0x2220B4CE, 0x953D75CA, 0x28803AF2, 0x9F9DFBF6, 0x46BBB8FB, 0xF1A679FF, + 0xF4F63EE1, 0x43EBFFE5, 0x9ACDBCE8, 0x2DD07DEC, 0x77708634, 0xC06D4730, + 0x194B043D, 0xAE56C539, 0xAB068227, 0x1C1B4323, 0xC53D002E, 0x7220C12A, + 0xCF9D8E12, 0x78804F16, 0xA1A60C1B, 0x16BBCD1F, 0x13EB8A01, 0xA4F64B05, + 0x7DD00808, 0xCACDC90C, 0x07AB9778, 0xB0B6567C, 0x69901571, 0xDE8DD475, + 0xDBDD936B, 0x6CC0526F, 0xB5E61162, 0x02FBD066, 0xBF469F5E, 0x085B5E5A, + 0xD17D1D57, 0x6660DC53, 0x63309B4D, 0xD42D5A49, 0x0D0B1944, 0xBA16D840, + 0x97C6A5AC, 0x20DB64A8, 0xF9FD27A5, 0x4EE0E6A1, 0x4BB0A1BF, 0xFCAD60BB, + 0x258B23B6, 0x9296E2B2, 0x2F2BAD8A, 0x98366C8E, 0x41102F83, 0xF60DEE87, + 0xF35DA999, 0x4440689D, 0x9D662B90, 0x2A7BEA94, 0xE71DB4E0, 0x500075E4, + 0x892636E9, 0x3E3BF7ED, 0x3B6BB0F3, 0x8C7671F7, 0x555032FA, 0xE24DF3FE, + 0x5FF0BCC6, 0xE8ED7DC2, 0x31CB3ECF, 0x86D6FFCB, 0x8386B8D5, 0x349B79D1, + 0xEDBD3ADC, 0x5AA0FBD8, 0xEEE00C69, 0x59FDCD6D, 0x80DB8E60, 0x37C64F64, + 0x3296087A, 0x858BC97E, 0x5CAD8A73, 0xEBB04B77, 0x560D044F, 0xE110C54B, + 0x38368646, 0x8F2B4742, 0x8A7B005C, 0x3D66C158, 0xE4408255, 0x535D4351, + 0x9E3B1D25, 0x2926DC21, 0xF0009F2C, 0x471D5E28, 0x424D1936, 0xF550D832, + 0x2C769B3F, 0x9B6B5A3B, 0x26D61503, 0x91CBD407, 0x48ED970A, 0xFFF0560E, + 0xFAA01110, 0x4DBDD014, 0x949B9319, 0x2386521D, 0x0E562FF1, 0xB94BEEF5, + 0x606DADF8, 0xD7706CFC, 0xD2202BE2, 0x653DEAE6, 0xBC1BA9EB, 0x0B0668EF, + 0xB6BB27D7, 0x01A6E6D3, 0xD880A5DE, 0x6F9D64DA, 0x6ACD23C4, 0xDDD0E2C0, + 0x04F6A1CD, 0xB3EB60C9, 0x7E8D3EBD, 0xC990FFB9, 0x10B6BCB4, 0xA7AB7DB0, + 0xA2FB3AAE, 0x15E6FBAA, 0xCCC0B8A7, 0x7BDD79A3, 0xC660369B, 0x717DF79F, + 0xA85BB492, 0x1F467596, 0x1A163288, 0xAD0BF38C, 0x742DB081, 0xC3307185, + 0x99908A5D, 0x2E8D4B59, 0xF7AB0854, 0x40B6C950, 0x45E68E4E, 0xF2FB4F4A, + 0x2BDD0C47, 0x9CC0CD43, 0x217D827B, 0x9660437F, 0x4F460072, 0xF85BC176, + 0xFD0B8668, 0x4A16476C, 0x93300461, 0x242DC565, 0xE94B9B11, 0x5E565A15, + 0x87701918, 0x306DD81C, 0x353D9F02, 0x82205E06, 0x5B061D0B, 0xEC1BDC0F, + 0x51A69337, 0xE6BB5233, 0x3F9D113E, 0x8880D03A, 0x8DD09724, 0x3ACD5620, + 0xE3EB152D, 0x54F6D429, 0x7926A9C5, 0xCE3B68C1, 0x171D2BCC, 0xA000EAC8, + 0xA550ADD6, 0x124D6CD2, 0xCB6B2FDF, 0x7C76EEDB, 0xC1CBA1E3, 0x76D660E7, + 0xAFF023EA, 0x18EDE2EE, 0x1DBDA5F0, 0xAAA064F4, 0x738627F9, 0xC49BE6FD, + 0x09FDB889, 0xBEE0798D, 0x67C63A80, 0xD0DBFB84, 0xD58BBC9A, 0x62967D9E, + 0xBBB03E93, 0x0CADFF97, 0xB110B0AF, 0x060D71AB, 0xDF2B32A6, 0x6836F3A2, + 0x6D66B4BC, 0xDA7B75B8, 0x035D36B5, 0xB440F7B1 +}; + +uint32_t mpeg_crc32(uint32_t crc, const uint8_t *buffer, uint32_t size) +{ + unsigned int i; + + for (i = 0; i < size; i++) { + crc = crc32table[(crc ^ buffer[i]) & 0xff] ^ (crc >> 8); + } + return crc ; +} diff --git a/MediaServer/libmpeg/source/mpeg-element-descriptor.c b/MediaServer/libmpeg/source/mpeg-element-descriptor.c new file mode 100644 index 0000000..d52657b --- /dev/null +++ b/MediaServer/libmpeg/source/mpeg-element-descriptor.c @@ -0,0 +1,719 @@ +// ITU-T H.222.0(06/2012) +// Information technology - Generic coding of moving pictures and associated audio information: Systems +// 2.6 Program and program element descriptors(p83) + +#include "mpeg-element-descriptor.h" +#include +#include +#include +#include + +/* +2.6 Program and program element descriptors +2.6.1 Semantic definition of fields in program and program element descriptors +Table 2-45 - Program and program element descriptors +tag TS PS Identification +0 n/a n/a reserved +1 n/a X forbidden +2 X X video_stream_descriptor +3 X X audio_stream_descriptor +4 X X hierarchy_descriptor +5 X X registration_descriptor +6 X X data_stream_alignment_descriptor +7 X X target_background_grid_descriptor +8 X X video_window_descriptor +9 X X CA_descriptor +10 X X ISO_639_language_descriptor +11 X X system_clock_descriptor +12 X X multiplex_buffer_utilization_descriptor +13 X X copyright_descriptor +14 X maximum_bitrate_descriptor +15 X X private_data_indicator_descriptor +16 X X smoothing_buffer_descriptor +17 X STD_descriptor +18 X X IBP_descriptor +19-26 X Defined in ISO/IEC 13818-6 +27 X X MPEG-4_video_descriptor +28 X X MPEG-4_audio_descriptor +29 X X IOD_descriptor +30 X SL_descriptor +31 X X FMC_descriptor +32 X X external_ES_ID_descriptor +33 X X MuxCode_descriptor +34 X X FmxBufferSize_descriptor +35 X multiplexbuffer_descriptor +36 X X content_labeling_descriptor +37 X X metadata_pointer_descriptor +38 X X metadata_descriptor +39 X X metadata_STD_descriptor +40 X X AVC video descriptor +41 X X IPMP_descriptor (defined in ISO/IEC 13818-11, MPEG-2 IPMP) +42 X X AVC timing and HRD descriptor +43 X X MPEG-2_AAC_audio_descriptor +44 X X FlexMuxTiming_descriptor +45 X X MPEG-4_text_descriptor +46 X X MPEG-4_audio_extension_descriptor +47 X X auxiliary_video_stream_descriptor +48 X X SVC extension descriptor +49 X X MVC extension descriptor +50 X n/a J2K video descriptor +51 X X MVC operation point descriptor +52 X X MPEG2_stereoscopic_video_format_descriptor +53 X X Stereoscopic_program_info_descriptor +54 X X Stereoscopic_video_info_descriptor +55 X n/a Transport_profile_descriptor +56 X n/a HEVC video descriptor +57-63 n/a n/a Rec. ITU-T H.222.0 | ISO/IEC 13818-1 Reserved +64-255 n/a n/a User Private +*/ +int mpeg_elment_descriptor(struct mpeg_bits_t* reader) +{ + size_t offset; + uint8_t tag = mpeg_bits_read8(reader); + uint8_t len = mpeg_bits_read8(reader); + if (mpeg_bits_error(reader)) + return -1; + + offset = mpeg_bits_tell(reader); + switch(tag) + { + case 2: + video_stream_descriptor(reader, len); + break; + + case 3: + audio_stream_descriptor(reader, len); + break; + + case 4: + hierarchy_descriptor(reader, len); + break; + + case 5: + registration_descriptor(reader, len); + break; + + case 10: + language_descriptor(reader, len); + break; + + case 11: + system_clock_descriptor(reader, len); + break; + + case 27: + mpeg4_video_descriptor(reader, len); + break; + + case 28: + mpeg4_audio_descriptor(reader, len); + break; + + case 37: + metadata_pointer_descriptor(reader, len); + break; + + case 38: + metadata_descriptor(reader, len); + break; + + case 40: + avc_video_descriptor(reader, len); + break; + + case 42: + avc_timing_hrd_descriptor(reader, len); + break; + + case 43: + mpeg2_aac_descriptor(reader, len); + break; + + case 48: + svc_extension_descriptor(reader, len); + break; + + case 49: + mvc_extension_descriptor(reader, len); + break; + + case 56: + hevc_video_descriptor(reader, len); + break; + + case 57: + vvc_video_descriptor(reader, len); + break; + + case 58: + evc_video_descriptor(reader, len); + break; + + case 0x40: + clock_extension_descriptor(reader, len); + break; + + //default: + // assert(0); + } + + mpeg_bits_seek(reader, offset + len); // read all + return mpeg_bits_error(reader) ? -1 : 0; +} + +int video_stream_descriptor(struct mpeg_bits_t* reader, uint8_t len) +{ + // 2.6.2 Video stream descriptor(p85) + + uint8_t v; + video_stream_descriptor_t desc; + + (void)len; assert(len >= 1); + memset(&desc, 0, sizeof(desc)); + v = mpeg_bits_read8(reader); + desc.multiple_frame_rate_flag = (v >> 7) & 0x01; + desc.frame_rate_code = (v >> 3) & 0x0F; + desc.MPEG_1_only_flag = (v >> 2) & 0x01; + desc.constrained_parameter_flag = (v >> 1) & 0x01; + desc.still_picture_flag = v & 0x01; + + if(0 == desc.MPEG_1_only_flag) + { + desc.profile_and_level_indication = mpeg_bits_read8(reader); + v = mpeg_bits_read8(reader); + desc.chroma_format = (v >> 6) & 0x03; + desc.frame_rate_code = (v >> 5) & 0x01; + assert((0x1F & v) == 0x00); // 'xxx00000' + } + + assert(0 == mpeg_bits_error(reader)); + return mpeg_bits_error(reader) ? -1 : 0; +} + +int audio_stream_descriptor(struct mpeg_bits_t* reader, uint8_t len) +{ + // 2.6.4 Audio stream descriptor(p86) + + uint8_t v; + audio_stream_descriptor_t desc; + + (void)len; assert(len >= 1); + v = mpeg_bits_read8(reader); + memset(&desc, 0, sizeof(desc)); + desc.free_format_flag = (v >> 7) & 0x01; + desc.ID = (v >> 6) & 0x01; + desc.layer = (v >> 4) & 0x03; + desc.variable_rate_audio_indicator = (v >> 3) & 0x01; + + assert(0 == mpeg_bits_error(reader)); + return mpeg_bits_error(reader) ? -1 : 0; +} + +int hierarchy_descriptor(struct mpeg_bits_t* reader, uint8_t len) +{ + // 2.6.6 Hierarchy descriptor(p86) + + uint8_t v; + hierarchy_descriptor_t desc; + + (void)len; assert(len >= 4); + v = mpeg_bits_read8(reader); + memset(&desc, 0, sizeof(desc)); + desc.no_view_scalability_flag = (v >> 7) & 0x01; + desc.no_temporal_scalability_flag = (v >> 6) & 0x01; + desc.no_spatial_scalability_flag = (v >> 5) & 0x01; + desc.no_quality_scalability_flag = (v >> 4) & 0x01; + desc.hierarchy_type = v & 0x0F; + desc.hierarchy_layer_index = mpeg_bits_read8(reader) & 0x3F; + v = mpeg_bits_read8(reader); + desc.tref_present_flag = (v >> 7) & 0x01; + desc.hierarchy_embedded_layer_index = v & 0x3F; + desc.hierarchy_channel = mpeg_bits_read8(reader) & 0x3F; + + assert(0 == mpeg_bits_error(reader)); + return mpeg_bits_error(reader) ? -1 : 0; +} + +int registration_descriptor(struct mpeg_bits_t* reader, uint8_t len) +{ + // 2.6.8 Registration descriptor(p94) + size_t fourcc; + + (void)len; assert(len >= 4); + fourcc = mpeg_bits_read32(reader); + (void)fourcc; + + assert(0 == mpeg_bits_error(reader)); + return mpeg_bits_error(reader) ? -1 : 0; +} + +int language_descriptor(struct mpeg_bits_t* reader, uint8_t len) +{ + // 2.6.18 ISO 639 language descriptor(p92) + uint8_t i; + uint32_t v; + + for (i = 0; i + 4 < len; i += 4) + { + language_descriptor_t desc; + memset(&desc, 0, sizeof(desc)); + + v = mpeg_bits_read32(reader); + desc.code = v >> 8; + desc.audio = v & 0xFF; + } + + return mpeg_bits_error(reader) ? -1 : 0; +} + +int system_clock_descriptor(struct mpeg_bits_t* reader, uint8_t len) +{ + // 2.6.20 System clock descriptor(p92) + + uint8_t v; + system_clock_descriptor_t desc; + + (void)len; assert(len >= 2); + v = mpeg_bits_read8(reader); + memset(&desc, 0, sizeof(desc)); + desc.external_clock_reference_indicator = (v >> 7) & 0x01; + desc.clock_accuracy_integer = v & 0x3F; + desc.clock_accuracy_exponent = (mpeg_bits_read8(reader) >> 5) & 0x07; + + assert(0 == mpeg_bits_error(reader)); + return mpeg_bits_error(reader) ? -1 : 0; +} + +int mpeg4_video_descriptor(struct mpeg_bits_t* reader, uint8_t len) +{ + // 2.6.36 MPEG-4 video descriptor(p96) + + mpeg4_video_descriptor_t desc; + + (void)len; assert(len >= 1); + memset(&desc, 0, sizeof(desc)); + desc.visual_profile_and_level = mpeg_bits_read8(reader); + + assert(0 == mpeg_bits_error(reader)); + return mpeg_bits_error(reader) ? -1 : 0; +} + +int mpeg4_audio_descriptor(struct mpeg_bits_t* reader, uint8_t len) +{ + // 2.6.38 MPEG-4 audio descriptor(p97) + + mpeg4_audio_descriptor_t desc; + + (void)len; assert(len >= 1); + memset(&desc, 0, sizeof(desc)); + desc.profile_and_level = mpeg_bits_read8(reader); + + assert(0 == mpeg_bits_error(reader)); + return mpeg_bits_error(reader) ? -1 : 0; +} + +int metadata_pointer_descriptor(struct mpeg_bits_t* reader, uint8_t len) +{ + // 2.6.58 Metadata pointer descriptor(p112) + + uint8_t flags; + metadata_pointer_descriptor_t desc; + + (void)len; assert(len >= 5); + desc.metadata_application_format_identifier = mpeg_bits_read16(reader); + if (0xFFFF == desc.metadata_application_format_identifier) + desc.metadata_application_format_identifier = mpeg_bits_read32(reader); + + desc.metadata_format_identifier = mpeg_bits_read8(reader); + if (0xFF == desc.metadata_format_identifier) + desc.metadata_format_identifier = mpeg_bits_read32(reader); + + desc.metadata_service_id = mpeg_bits_read8(reader); + flags = mpeg_bits_read8(reader); + desc.MPEG_carriage_flags = (flags >> 5) & 0x03; + + if (flags & 0x80) // metadata_locator_record_flag + { + desc.metadata_locator_record_length = mpeg_bits_read8(reader); + mpeg_bits_skip(reader, desc.metadata_locator_record_length); // metadata_locator_record_byte + } + + if (desc.MPEG_carriage_flags <= 2) + desc.program_number = mpeg_bits_read16(reader); + + if (1 == desc.MPEG_carriage_flags) + { + desc.transport_stream_location = mpeg_bits_read16(reader); + desc.transport_stream_id = mpeg_bits_read16(reader); + } + + assert(0 == mpeg_bits_error(reader)); + return mpeg_bits_error(reader) ? -1 : 0; +} + +int metadata_descriptor(struct mpeg_bits_t* reader, uint8_t len) +{ + // 2.6.60 Metadata descriptor(p115) + + uint8_t flags; + metadata_descriptor_t desc; + + (void)len; assert(len >= 5); + desc.metadata_application_format_identifier = mpeg_bits_read16(reader); + if (0xFFFF == desc.metadata_application_format_identifier) + desc.metadata_application_format_identifier = mpeg_bits_read32(reader); + + desc.metadata_format_identifier = mpeg_bits_read8(reader); + if (0xFF == desc.metadata_format_identifier) + desc.metadata_format_identifier = mpeg_bits_read32(reader); + + desc.metadata_service_id = mpeg_bits_read8(reader); + flags = mpeg_bits_read8(reader); + desc.decoder_config_flags = (flags >> 5) & 0x07; + if (flags & 0x10) // DSM-CC_flag + { + desc.service_identification_length = mpeg_bits_read8(reader); + mpeg_bits_skip(reader, desc.service_identification_length); // service_identification_record_byte + } + + if (0x01 == desc.decoder_config_flags) + { + desc.decoder_config_length = mpeg_bits_read8(reader); + mpeg_bits_skip(reader, desc.decoder_config_length); // decoder_config_byte + } + else if (0x03 == desc.decoder_config_flags) + { + desc.dec_config_identification_record_length = mpeg_bits_read8(reader); + mpeg_bits_skip(reader, desc.dec_config_identification_record_length); // dec_config_identification_record_byte + } + else if (0x04 == desc.decoder_config_flags) + { + desc.decoder_config_metadata_service_id = mpeg_bits_read8(reader); + } + + assert(0 == mpeg_bits_error(reader)); + return mpeg_bits_error(reader) ? -1 : 0; +} + +int avc_video_descriptor(struct mpeg_bits_t* reader, uint8_t len) +{ + // 2.6.64 AVC video descriptor(p110) + + uint8_t v; + avc_video_descriptor_t desc; + + (void)len; assert(len >= 4); + memset(&desc, 0, sizeof(desc)); + desc.profile_idc = mpeg_bits_read8(reader); + v = mpeg_bits_read8(reader); + desc.constraint_set0_flag = (v >> 7) & 0x01; + desc.constraint_set1_flag = (v >> 6) & 0x01; + desc.constraint_set2_flag = (v >> 5) & 0x01; + desc.constraint_set3_flag = (v >> 4) & 0x01; + desc.constraint_set4_flag = (v >> 3) & 0x01; + desc.constraint_set5_flag = (v >> 2) & 0x01; + desc.AVC_compatible_flags = v & 0x03; + desc.level_idc = mpeg_bits_read8(reader); + v = mpeg_bits_read8(reader); + desc.AVC_still_present = (v >> 7) & 0x01; + desc.AVC_24_hour_picture_flag = (v >> 6) & 0x01; + desc.frame_packing_SEI_not_present_flag = (v >> 5) & 0x01; + + assert(0 == mpeg_bits_error(reader)); + return mpeg_bits_error(reader) ? -1 : 0; +} + +int avc_timing_hrd_descriptor(struct mpeg_bits_t* reader, uint8_t len) +{ + // 2.6.66 AVC timing and HRD descriptor(p112) + + uint8_t v; + avc_timing_hrd_descriptor_t desc; + + (void)len; assert(len >= 2); + memset(&desc, 0, sizeof(desc)); + v = mpeg_bits_read8(reader); + desc.hrd_management_valid_flag = (v >> 7) & 0x01; + desc.picture_and_timing_info_present = (v >> 0) & 0x01; + if(desc.picture_and_timing_info_present) + { + v = mpeg_bits_read8(reader); + desc._90kHZ_flag = (v >> 7) & 0x01; + if(0 == desc._90kHZ_flag) + { + desc.N = mpeg_bits_read32(reader); + desc.K = mpeg_bits_read32(reader); + } + desc.num_unit_in_tick = mpeg_bits_read32(reader); + } + + v = mpeg_bits_read8(reader); + desc.fixed_frame_rate_flag = (v >> 7) & 0x01; + desc.temporal_poc_flag = (v >> 6) & 0x01; + desc.picture_to_display_conversion_flag = (v >> 5) & 0x01; + + assert(0 == mpeg_bits_error(reader)); + return mpeg_bits_error(reader) ? -1 : 0; +} + +int mpeg2_aac_descriptor(struct mpeg_bits_t* reader, uint8_t len) +{ + // 2.6.68 MPEG-2 AAC audio descriptor(p113) + + mpeg2_aac_descriptor_t desc; + + (void)len; assert(len >= 3); + memset(&desc, 0, sizeof(desc)); + desc.profile = mpeg_bits_read8(reader); + desc.channel_configuration = mpeg_bits_read8(reader); + desc.additional_information = mpeg_bits_read8(reader); + + assert(0 == mpeg_bits_error(reader)); + return mpeg_bits_error(reader) ? -1 : 0; +} + +int svc_extension_descriptor(struct mpeg_bits_t* reader, uint8_t len) +{ + // 2.6.76 SVC extension descriptor(p116) + + uint8_t v; + svc_extension_descriptor_t desc; + + (void)len; assert(len >= 13); + memset(&desc, 0, sizeof(desc)); + desc.width = mpeg_bits_read16(reader); + desc.height = mpeg_bits_read16(reader); + desc.frame_rate = mpeg_bits_read16(reader); + desc.average_bitrate = mpeg_bits_read16(reader); + desc.maximum_bitrate = mpeg_bits_read16(reader); + desc.dependency_id = (mpeg_bits_read8(reader) >> 5) & 0x07; + v = mpeg_bits_read8(reader); + desc.quality_id_start = (v >> 4) & 0x0F; + desc.quality_id_end = (v >> 0) & 0x0F; + v = mpeg_bits_read8(reader); + desc.temporal_id_start = (v >> 5) & 0x07; + desc.temporal_id_end = (v >> 2) & 0x07; + desc.no_sei_nal_unit_present = (v >> 1) & 0x01; + + assert(0 == mpeg_bits_error(reader)); + return mpeg_bits_error(reader) ? -1 : 0; +} + +int mvc_extension_descriptor(struct mpeg_bits_t* reader, uint8_t len) +{ + // 2.6.78 MVC extension descriptor(p117) + + uint32_t v; + mvc_extension_descriptor_t desc; + + (void)len; assert(len >= 8); + memset(&desc, 0, sizeof(desc)); + desc.average_bit_rate = mpeg_bits_read16(reader); + desc.maximum_bitrate = mpeg_bits_read16(reader); + v = mpeg_bits_read32(reader); + desc.view_order_index_min = (v >> 18) & 0x3FF; + desc.view_order_index_max = (v >> 8) & 0x3FF; + desc.temporal_id_start = (v >> 5) & 0x07; + desc.temporal_id_end = (v >> 2) & 0x07; + desc.no_sei_nal_unit_present = (v >> 1) & 0x01; + desc.no_prefix_nal_unit_present = (v >> 0) & 0x01; + + assert(0 == mpeg_bits_error(reader)); + return mpeg_bits_error(reader) ? -1 : 0; +} + +int hevc_video_descriptor(struct mpeg_bits_t* reader, uint8_t len) +{ + // 2.6.95 HEVC video descriptor(p146) + + uint64_t v; + hevc_video_descriptor_t desc; + + (void)len; assert(len >= 13); + memset(&desc, 0, sizeof(desc)); + v = mpeg_bits_read8(reader); + desc.profile_space = (v >> 6) & 0x03; + desc.tier_flag = (v >> 5) & 0x01; + desc.profile_idc = (v >> 0) & 0x1F; + desc.profile_compatibility_indication = mpeg_bits_read32(reader); + v = mpeg_bits_read64(reader); + desc.progressive_source_flag = (v >> 63) & 0x01; + desc.interlaced_source_flag = (v >> 62) & 0x01; + desc.non_packed_constraint_flag = (v >> 61) & 0x01; + desc.frame_only_constraint_flag = (v >> 60) & 0x01; + desc.copied_44bits = (v >> 16) & 0xFFFFFFFFFFFULL; + desc.level_idc = (v >> 8) & 0xFF; + desc.temporal_layer_subset_flag = (v >> 7) & 0x01; + desc.HEVC_still_present_flag = (v >> 6) & 0x01; + desc.HEVC_24hr_picture_present_flag = (v >> 5) & 0x01; + desc.sub_pic_hrd_params_not_present_flag = (v >> 4) & 0x01; + desc.HDR_WCG_idc = v & 0x03; + if (desc.temporal_layer_subset_flag) { + v = mpeg_bits_read8(reader); + desc.temporal_id_min = (v >> 5) & 0x07; + v = mpeg_bits_read8(reader); + desc.temporal_id_max = (v >> 5) & 0x07; + } + + assert(0 == mpeg_bits_error(reader)); + return mpeg_bits_error(reader) ? -1 : 0; +} + +int vvc_video_descriptor(struct mpeg_bits_t* reader, uint8_t len) +{ + // 2.6.129 VVC video descriptor(p172) + + int i; + uint8_t v; + vvc_video_descriptor_t desc; + + (void)len; assert(len >= 6); + memset(&desc, 0, sizeof(desc)); + v = mpeg_bits_read8(reader); + desc.profile_idc = (v >> 1) & 0x7F; + desc.tier_flag = v & 0x01; + desc.num_sub_profiles = mpeg_bits_read8(reader); + for(i = 0; i < desc.num_sub_profiles && i < sizeof(desc.sub_profile_idc)/sizeof(desc.sub_profile_idc[0]); i++) + desc.sub_profile_idc[i] = mpeg_bits_read32(reader); + + v = mpeg_bits_read8(reader); + desc.progressive_source_flag = (v >> 7) & 0x01; + desc.interlaced_source_flag = (v >> 6) & 0x01; + desc.non_packed_constraint_flag = (v >> 5) & 0x01; + desc.frame_only_constraint_flag = (v >> 4) & 0x01; + desc.reserved_zero_4bits = (v >> 0) & 0x0F; + desc.level_idc = mpeg_bits_read8(reader); + + v = mpeg_bits_read8(reader); + desc.temporal_layer_subset_flag = (v >> 7) & 0x01; + desc.VVC_still_present_flag = (v >> 6) & 0x01; + desc.VVC_24hr_picture_present_flag = (v >> 5) & 0x01; + + v = mpeg_bits_read8(reader); + desc.HDR_WCG_idc = (v >> 6) & 0x03; + desc.video_properties_tag = v & 0x0F; + + if (desc.temporal_layer_subset_flag) { + v = mpeg_bits_read8(reader); + desc.temporal_id_min = v & 0x07; + v = mpeg_bits_read8(reader); + desc.temporal_id_max = v & 0x07; + } + + assert(0 == mpeg_bits_error(reader)); + return mpeg_bits_error(reader) ? -1 : 0; +} + +int evc_video_descriptor(struct mpeg_bits_t* reader, uint8_t len) +{ + // 2.6.133 EVC video descriptor(p176) + + uint8_t v; + evc_video_descriptor_t desc; + + (void)len; assert(len >= 6); + memset(&desc, 0, sizeof(desc)); + desc.profile_idc = mpeg_bits_read8(reader); + desc.level_idc = mpeg_bits_read8(reader); + desc.toolset_idc_h = mpeg_bits_read32(reader); + desc.toolset_idc_l = mpeg_bits_read32(reader); + + v = mpeg_bits_read8(reader); + desc.progressive_source_flag = (v >> 7) & 0x01; + desc.interlaced_source_flag = (v >> 6) & 0x01; + desc.non_packed_constraint_flag = (v >> 5) & 0x01; + desc.frame_only_constraint_flag = (v >> 4) & 0x01; + desc.reserved = (v >> 3) & 0x01; + desc.temporal_layer_subset_flag = (v >> 2) & 0x01; + desc.EVC_still_present_flag = (v >> 1) & 0x01; + desc.EVC_24hr_picture_present_flag = (v >> 0) & 0x01; + + v = mpeg_bits_read8(reader); + desc.HDR_WCG_idc = (v >> 6) & 0x03; + desc.video_properties_tag = v & 0x0F; + + if (desc.temporal_layer_subset_flag) { + v = mpeg_bits_read8(reader); + desc.temporal_id_min = v & 0x07; + v = mpeg_bits_read8(reader); + desc.temporal_id_max = v & 0x07; + } + + assert(0 == mpeg_bits_error(reader)); + return mpeg_bits_error(reader) ? -1 : 0; +} + +size_t service_extension_descriptor_write(uint8_t* data, size_t bytes) +{ + uint8_t n; + n = (uint8_t)strlen(SERVICE_NAME); + if (bytes < 2 + n) + return 0; + data[0] = SERVICE_ID; + data[1] = n; + memcpy(data + 2, SERVICE_NAME, n); + return 2 + n; +} + +typedef struct _clock_extension_descriptor_t +{ + uint8_t year; // base 2000, 8-bit + uint8_t month; // 1-12, 4-bit + uint8_t day; // 1-31, 5-bit + uint8_t hour; // 0-23, 5-bit + uint8_t minute; // 0-59, 6-bit + uint8_t second; // 0-59, 6-bit + uint16_t microsecond; // 14-bit +} clock_extension_descriptor_t; + +int clock_extension_descriptor(struct mpeg_bits_t* reader, uint8_t len) +{ + uint32_t v; + struct tm t; + time_t clock; + + (void)len; assert(len >= 9); + v = mpeg_bits_read32(reader); // skip 4-bytes leading + memset(&t, 0, sizeof(t)); + t.tm_year = mpeg_bits_read8(reader) + 2000 - 1900; + v = mpeg_bits_read32(reader); + t.tm_mon = ((v >> 28) & 0x0F) - 1; + t.tm_mday = (v >> 23) & 0x1F; + t.tm_hour = (v >> 18) & 0x1F; + t.tm_min = (v >> 12) & 0x3F; + t.tm_sec = (v >> 6) & 0x3F; + //desc.microsecond = v & 0x3F; + clock = mktime(&t) * 1000; + + assert(0 == mpeg_bits_error(reader)); + return mpeg_bits_error(reader) ? -1 : 0; +} + +size_t clock_extension_descriptor_write(uint8_t* data, size_t bytes, int64_t clock) +{ + struct tm* t; + time_t seconds; + if (bytes < 16) + return 0; + + seconds = (time_t)(clock / 1000); + t = localtime(&seconds); + + data[0] = 0x40; + data[1] = 0x0E; + data[2] = 0x48; + data[3] = 0x4B; + data[4] = 0x01; + data[5] = 0x00; + data[6] = (uint8_t)(t->tm_year + 1900 - 2000); // base 2000 + data[7] = (uint8_t)((t->tm_mon + 1) << 4) | ((t->tm_mday >> 1) & 0x0F); + data[8] = (uint8_t)((t->tm_mday & 0x01) << 7) | ((t->tm_hour & 0x1F) << 2) | ((t->tm_min >> 4) & 0x03); + data[9] = (uint8_t)((t->tm_min & 0x0F) << 4) | ((t->tm_sec >> 2) & 0x0F); + data[10] = (uint8_t)((t->tm_sec & 0x03) << 6); + data[11] = 0x00; + data[12] = 0x00; + data[13] = 0xFF; + data[14] = 0xFF; + data[15] = 0xFF; + return 16; +} diff --git a/MediaServer/libmpeg/source/mpeg-muxer.c b/MediaServer/libmpeg/source/mpeg-muxer.c new file mode 100644 index 0000000..634b35d --- /dev/null +++ b/MediaServer/libmpeg/source/mpeg-muxer.c @@ -0,0 +1,105 @@ +#include +#include +#include "mpeg-muxer.h" + +struct mpeg_muxer_t { + int is_ps; + union { + struct { + void *ctx; + void *param; + mpeg_muxer_func_t func; + } ts; + struct ps_muxer_t *ps; + } u; +}; + +static void *on_mpeg_ts_alloc(void *param, size_t bytes) { + mpeg_muxer_t *mpeg = (mpeg_muxer_t *) param; + return mpeg->u.ts.func.alloc(mpeg->u.ts.param, bytes); +} + +static void on_mpeg_ts_free(void *param, void *packet) { + mpeg_muxer_t *mpeg = (mpeg_muxer_t *) param; + mpeg->u.ts.func.free(mpeg->u.ts.param, packet); +} + +static int on_mpeg_ts_write(void *param, const void *packet, size_t bytes) { + mpeg_muxer_t *mpeg = (mpeg_muxer_t *) param; + return mpeg->u.ts.func.write(mpeg->u.ts.param, 0, (void *) packet, bytes); +} + +mpeg_muxer_t *mpeg_muxer_create(int is_ps, const mpeg_muxer_func_t *func, void *param) { + mpeg_muxer_t *mpeg = (mpeg_muxer_t *) malloc(sizeof(mpeg_muxer_t)); + assert(mpeg); + mpeg->is_ps = is_ps; + if (is_ps) { + mpeg->u.ps = ps_muxer_create(func, param); + } else { + struct mpeg_ts_func_t ts_func = {on_mpeg_ts_alloc, on_mpeg_ts_free, on_mpeg_ts_write}; + mpeg->u.ts.func = *func; + mpeg->u.ts.param = param; + mpeg->u.ts.ctx = mpeg_ts_create(&ts_func, mpeg); + } + return mpeg; +} + +int mpeg_muxer_destroy(mpeg_muxer_t *muxer) { + assert(muxer); + int ret = -1; + if (muxer->is_ps) { + ret = ps_muxer_destroy(muxer->u.ps); + } else { + ret = mpeg_ts_destroy(muxer->u.ts.ctx); + } + free(muxer); + return ret; +} + +int mpeg_muxer_add_stream(mpeg_muxer_t *muxer, int codecid, const void *extradata, size_t extradata_size) { + assert(muxer); + if (muxer->is_ps) { + return ps_muxer_add_stream(muxer->u.ps, codecid, extradata, extradata_size); + } + return mpeg_ts_add_stream(muxer->u.ts.ctx, codecid, extradata, extradata_size); +} + +int mpeg_muxer_input(mpeg_muxer_t *muxer, int stream, int flags, int64_t pts, int64_t dts, const void *data, size_t bytes) { + assert(muxer); + if (muxer->is_ps) { + return ps_muxer_input(muxer->u.ps, stream, flags, pts, dts, data, bytes); + } + return mpeg_ts_write(muxer->u.ts.ctx, stream, flags, pts, dts, data, bytes); +} + +int mpeg_muxer_reset(mpeg_muxer_t *muxer) { + assert(muxer); + if (muxer->is_ps) { + return -1; + } + return mpeg_ts_reset(muxer->u.ts.ctx); +} + +int mpeg_muxer_add_program(mpeg_muxer_t *muxer, uint16_t pn, const void *info, int bytes) { + assert(muxer); + if (muxer->is_ps) { + return -1; + } + return mpeg_ts_add_program(muxer->u.ts.ctx, pn, info, bytes); +} + +int mpeg_muxer_remove_program(mpeg_muxer_t *muxer, uint16_t pn) { + assert(muxer); + if (muxer->is_ps) { + return -1; + } + return mpeg_ts_remove_program(muxer->u.ts.ctx, pn); +} + +int mpeg_muxer_add_program_stream(mpeg_muxer_t *muxer, uint16_t pn, int codecid, const void *extra_data, size_t extra_data_size) { + assert(muxer); + if (muxer->is_ps) { + return -1; + } + return mpeg_ts_add_program_stream(muxer->u.ts.ctx, pn, codecid, extra_data, extra_data_size); +} \ No newline at end of file diff --git a/MediaServer/libmpeg/source/mpeg-pack-header.c b/MediaServer/libmpeg/source/mpeg-pack-header.c new file mode 100644 index 0000000..8444384 --- /dev/null +++ b/MediaServer/libmpeg/source/mpeg-pack-header.c @@ -0,0 +1,137 @@ +#include "mpeg-ps-internal.h" +#include + +// 2.5.3.3 Pack layer of program stream (p78) +// Table 2-38 - Program stream pack +// Table 2-39 - Program stream pack header +#if 1 +int pack_header_read(struct ps_pack_header_t* h, struct mpeg_bits_t* reader) +{ + uint8_t v8; + uint64_t v64; + uint8_t stuffing_length; + size_t header_length; + + v8 = mpeg_bits_read8(reader); // data[4] + if (0 == (0xC0 & v8)) + { + // MPEG-1 + // ISO/IEC 11172-1 + /* + pack() { + pack_start_code 32 bslbf + '0010' 4 bslbf + system_clock_reference [32..30] 3 bslbf + marker_bit 1 bslbf + system_clock_reference [29..15] 15 bslbf + marker_bit 1 bslbf + system_clock_reference [14..0] 15 bslbf + marker_bit 1 bslbf + marker_bit 1 bslbf + mux_rate 22 uimsbf + marker_bit 1 bslbf + if (nextbits() == system_header_start_code) + system_header () + while (nextbits() == packet_start_code) + packet() + } + */ + h->mpeg2 = 0; + //assert(0x20 == (0xF0 & v8)); + h->system_clock_reference_base = (((uint64_t)(v8 >> 1) & 0x07) << 30) | mpeg_bits_read30(reader); + h->system_clock_reference_extension = 1; + h->program_mux_rate = (mpeg_bits_read8(reader) & 0x7F) << 15; + h->program_mux_rate |= mpeg_bits_read15(reader); + return mpeg_bits_error(reader) ? MPEG_ERROR_NEED_MORE_DATA : MPEG_ERROR_OK; + } + else + { + h->mpeg2 = 1; + v64 = mpeg_bits_read64(reader); // data[5-12] + //assert((0x44 & data[4]) == 0x44); // '01xxx1xx' + //assert((0x04 & data[6]) == 0x04); // 'xxxxx1xx' + //assert((0x04 & data[8]) == 0x04); // 'xxxxx1xx' + //assert((0x01 & data[9]) == 0x01); // 'xxxxxxx1' + h->system_clock_reference_base = (((v8 >> 3) & 0x07) << 30) | ((v8 & 0x03) << 28) | (((v64 >> 51) & 0x1FFF) << 15) | ((v64 >> 35) & 0x7FFF); + h->system_clock_reference_extension = (uint32_t)((v64 >> 25) & 0x1F); + + //assert((0x03 & v64) == 0x03); // 'xxxxxx11' + h->program_mux_rate = (uint32_t)((v64 >> 2) & 0x3FFFFF); + + //assert((0xF8 & data[13]) == 0x00); // '00000xxx' + stuffing_length = mpeg_bits_read8(reader) & 0x07; // stuffing + + header_length = 14 + stuffing_length; + mpeg_bits_skip(reader, stuffing_length); + return mpeg_bits_error(reader) ? MPEG_ERROR_NEED_MORE_DATA : MPEG_ERROR_OK; + } +} + +#else +size_t pack_header_read(struct ps_pack_header_t *h, const uint8_t* data, size_t bytes) +{ + uint8_t stuffing_length; + size_t header_length; + + if (bytes < 14) return 0; + assert(0x00 == data[0] && 0x00 == data[1] && 0x01 == data[2] && PES_SID_START == data[3]); + if (0 == (0xC0 & data[4])) + { + // MPEG-1 + h->mpeg2 = 0; + assert(0x20 == (0xF0 & data[4])); + h->system_clock_reference_base = (((uint64_t)(data[4] >> 1) & 0x07) << 30) | ((uint64_t)data[5] << 22) | (((uint64_t)data[6] >> 1) << 15) | ((uint64_t)data[7] << 7) | (data[8] >> 1); + h->system_clock_reference_extension = 1; + h->program_mux_rate = ((data[9] >> 1) << 15) | (data[10] << 7) | (data[11] >> 1); + return 12; + } + else + { + h->mpeg2 = 1; + assert((0x44 & data[4]) == 0x44); // '01xxx1xx' + assert((0x04 & data[6]) == 0x04); // 'xxxxx1xx' + assert((0x04 & data[8]) == 0x04); // 'xxxxx1xx' + assert((0x01 & data[9]) == 0x01); // 'xxxxxxx1' + h->system_clock_reference_base = (((uint64_t)(data[4] >> 3) & 0x07) << 30) | (((uint64_t)data[4] & 0x3) << 28) | ((uint64_t)data[5] << 20) | ((((uint64_t)data[6] >> 3) & 0x1F) << 15) | (((uint64_t)data[6] & 0x3) << 13) | ((uint64_t)data[7] << 5) | ((data[8] >> 3) & 0x1F); + h->system_clock_reference_extension = ((data[8] & 0x3) << 7) | ((data[9] >> 1) & 0x7F); + + assert((0x03 & data[12]) == 0x03); // 'xxxxxx11' + h->program_mux_rate = (data[10] << 14) | (data[11] << 6) | ((data[12] >> 2) & 0x3F); + + //assert((0xF8 & data[13]) == 0x00); // '00000xxx' + stuffing_length = data[13] & 0x07; // stuffing + + header_length = 14 + stuffing_length; + if (header_length > bytes) + return 0; + return header_length; + } +} +#endif + +size_t pack_header_write(const struct ps_pack_header_t *h, uint8_t *data) +{ + // pack_start_code + nbo_w32(data, 0x000001BA); + + // 33-system_clock_reference_base + 9-system_clock_reference_extension + // '01xxx1xx xxxxxxxx xxxxx1xx xxxxxxxx xxxxx1xx xxxxxxx1' + data[4] = 0x44 | (((h->system_clock_reference_base >> 30) & 0x07) << 3) | ((h->system_clock_reference_base >> 28) & 0x03); + data[5] = ((h->system_clock_reference_base >> 20) & 0xFF); + data[6] = 0x04 | (((h->system_clock_reference_base >> 15) & 0x1F) << 3) | ((h->system_clock_reference_base >> 13) & 0x03); + data[7] = ((h->system_clock_reference_base >> 5) & 0xFF); + data[8] = 0x04 | ((h->system_clock_reference_base & 0x1F) << 3) | ((h->system_clock_reference_extension >> 7) & 0x03); + data[9] = 0x01 | ((h->system_clock_reference_extension & 0x7F) << 1); + + // program_mux_rate + // 'xxxxxxxx xxxxxxxx xxxxxx11' + data[10] = (uint8_t)(h->program_mux_rate >> 14); + data[11] = (uint8_t)(h->program_mux_rate >> 6); + data[12] = (uint8_t)(0x03 | ((h->program_mux_rate & 0x3F) << 2)); + + // stuffing length + // '00000xxx' + data[13] = 0xF8; + + return 14; +} diff --git a/MediaServer/libmpeg/source/mpeg-packet.c b/MediaServer/libmpeg/source/mpeg-packet.c new file mode 100644 index 0000000..bf1ab10 --- /dev/null +++ b/MediaServer/libmpeg/source/mpeg-packet.c @@ -0,0 +1,243 @@ +#include "mpeg-pes-internal.h" +#include +#include +#include +#include + +#define MPEG_PACKET_PAYLOAD_MAX_SIZE (10 * 1024 * 1024) + +typedef int (*h2645_find_new_access)(const uint8_t* p, size_t bytes, int* vcl); + +static int mpeg_packet_append(struct packet_t* pkt, const void* data, size_t size) +{ + void* ptr; + + // fix: pkt->size + size bits wrap + if (pkt->size + size > MPEG_PACKET_PAYLOAD_MAX_SIZE || pkt->size + size < pkt->size) + return -EINVAL; + + if (pkt->capacity < pkt->size + size) + { + ptr = realloc(pkt->data, pkt->size + size + 2048); + if (NULL == ptr) return -ENOMEM; + pkt->data = (uint8_t*)ptr; + pkt->capacity = pkt->size + size + 2048; + } + + // append new data + memcpy(pkt->data + pkt->size, data, size); + pkt->size += size; + return 0; +} + +static int mpeg_packet_h264_h265_filter(uint16_t program, uint16_t stream, struct packet_t* pkt, const uint8_t* data, size_t size, pes_packet_handler handler, void* param) +{ + int i; + size_t off; + size_t leading; + + //assert(0 == pes->len || pes->payload.len == pes->len); + + // skip AUD + for (off = i = 0; off < size; off += i + 1) + { + i = mpeg_h264_find_nalu(data + off, size - off, &leading); + if (i < 0) + { + assert(0); + return -1; + } + + //assert(0 == i - leading); + if (PSI_STREAM_H264 == pkt->codecid ? 9 == (data[off + i] & 0x1f) : 35 == ((data[off + i] >> 1) & 0x3f)) + continue; + + i -= (int)leading; // rewind to 0x00 00 00 01 + break; + } + + // TODO: check size > 0 ??? + return handler(param, program, stream, pkt->codecid, pkt->flags, pkt->pts, pkt->dts, data + off + i, size - off - i); +} + +// @param[out] consume used of new append data +static int mpeg_packet_h26x(struct packet_t* pkt, const struct pes_t* pes, size_t size, size_t* consume, pes_packet_handler handler, void* param) +{ + int r, n; + const uint8_t* p, *end, *data; + h2645_find_new_access find; + + n = PSI_STREAM_H264 == pes->codecid ? 4 : 5; + data = pkt->data; + end = pkt->data + pkt->size; + p = pkt->size - size < n ? pkt->data : end - size - n; // start from trailing nalu + + // TODO: The first frame maybe not a valid frame, filter it + + if (0 == pkt->codecid) + { + pkt->pts = pes->pts; + pkt->dts = pes->dts; + pkt->sid = pes->sid; + pkt->codecid = pes->codecid; + pkt->flags = pes->flags; + } + + // PES contain multiple packet + find = PSI_STREAM_H264 == pkt->codecid ? mpeg_h264_find_new_access_unit : (PSI_STREAM_H265 == pkt->codecid ? mpeg_h265_find_new_access_unit : mpeg_h266_find_new_access_unit); + n = find(p, end - p, &pkt->vcl); + while (n >= 0) + { + assert(pkt->vcl > 0); + if (MPEG_VCL_CORRUPT == pkt->vcl) + { + // video data contain 00 00 01 BA + // maybe previous packet data lost + r = (p + n - pkt->data) - (pkt->size - *consume); + assert(r >= 0 && r <= *consume); // r == 0: previous packet lost, new start code find + *consume = (r < 0 || r > *consume) ? *consume : r; + pkt->flags |= MPEG_FLAG_PACKET_CORRUPT; + pkt->size = 0; // clear + pkt->vcl = 0; + // todo: handle packet data ??? + return 0; + } + + p += n; + pkt->flags = (pkt->flags & (~MPEG_FLAG_IDR_FRAME)) | (1 == pkt->vcl ? MPEG_FLAG_IDR_FRAME : 0); // update key frame flags + r = mpeg_packet_h264_h265_filter(pes->pn, pes->pid, pkt, data, p - data, handler, param); + if (0 != r) + return r; + + data = p; + pkt->vcl = 0; // next frame + n = find(p, end - p, &pkt->vcl); + } + + // save pts/dts + pkt->pts = pes->pts; + pkt->dts = pes->dts; + pkt->sid = pes->sid; + pkt->flags = pes->flags; +// assert(0 == find(p, end - p)); // start with AUD + +#if !defined(MPEG_KEDA_H265_FROM_H264) + pkt->codecid = pes->codecid; +#else + // fix: keda h.265 stream psm codec id incorrect, e.g. psm codec id 36 -> 27 -> 27 -> 27 + if (pkt->codecid != pes->codecid && 0 == mpeg_h26x_verify(data, end - data, &r)) + { + static const uint8_t sc_codecid[] = { PSI_STREAM_RESERVED, PSI_STREAM_H264, PSI_STREAM_H265, PSI_STREAM_H266, PSI_STREAM_MPEG4, }; + pkt->codecid = sc_codecid[(r < 0 || r >= sizeof(sc_codecid) / sizeof(sc_codecid[0])) ? PSI_STREAM_RESERVED : r]; + } +#endif + + // remain data + if (data != pkt->data) + { + memmove(pkt->data, data, end - data); + pkt->size = end - data; + } + + return 0; +} + +static void pes_packet_codec_verify(struct pes_t* pes, struct packet_t* pkt) +{ + int r; + size_t i, n; + +#if defined(MPEG_GUESS_STREAM) || defined(MPEG_H26X_VERIFY) + if (pes->codecid == PSI_STREAM_RESERVED && 0 == mpeg_h26x_verify(pkt->data, pkt->size, &r)) + { + // modify codecid + static const uint8_t sc_codecid[] = { PSI_STREAM_RESERVED, PSI_STREAM_H264, PSI_STREAM_H265, PSI_STREAM_H266, PSI_STREAM_MPEG4, }; + pkt->codecid = pes->codecid = sc_codecid[(r < 0 || r >= sizeof(sc_codecid) / sizeof(sc_codecid[0])) ? PSI_STREAM_RESERVED : r]; + } +#endif + +#if defined(MPEG_DAHUA_AAC_FROM_G711) + if ((pes->codecid == PSI_STREAM_AUDIO_G711A || pes->codecid == PSI_STREAM_AUDIO_G711U) + && pkt->size > 7 && 0xFF == pkt->data[0] && 0xF0 == (pkt->data[1] & 0xF0)) + { + n = 7; + // calc mpeg4_aac_adts_frame_length + for (i = 0; i + 7 < pkt->size && n >= 7; i += n) + { + // fix n == 0 + n = ((size_t)(pkt->data[i + 3] & 0x03) << 11) | ((size_t)pkt->data[i + 4] << 3) | ((size_t)(pkt->data[i + 5] >> 5) & 0x07); + } + pkt->codecid = pes->codecid = i == pkt->size ? PSI_STREAM_AAC : pes->codecid; // fix it + } +#endif +} + +int pes_packet(struct packet_t* pkt, struct pes_t* pes, const void* data, size_t size, size_t* consume, int start, pes_packet_handler handler, void* param) +{ + int r; + size_t total; + + total = size; + *consume = size; // all saved + // use timestamp to split packet + assert(PTS_NO_VALUE != pes->dts); + if (pkt->size > 0 && (pkt->dts != pes->dts || start) + // WARNING: don't use pes->codecid + && PSI_STREAM_H264 != pkt->codecid && PSI_STREAM_H265 != pkt->codecid && PSI_STREAM_H266 != pkt->codecid) + { + if(0 == pes->codecid) + pes_packet_codec_verify(pes, pkt); // verify on packet complete + + if (PSI_STREAM_H264 != pes->codecid && PSI_STREAM_H265 != pes->codecid && PSI_STREAM_H266 != pes->codecid) + { + assert(PTS_NO_VALUE != pkt->dts); + r = handler(param, pes->pn, pes->pid, pkt->codecid, pkt->flags, pkt->pts, pkt->dts, pkt->data, pkt->size); + pkt->size = 0; // new packet start + if (0 != r) + return r; + } + else + { + assert(0 == pkt->codecid); + pkt->codecid = pes->codecid; // update previous packet codec id + total += pkt->size; // find nalu vcl + } + } + + // merge buffer + r = mpeg_packet_append(pkt, data, size); + if (0 != r) + return r; + + if (PSI_STREAM_H264 == pes->codecid || PSI_STREAM_H265 == pes->codecid || PSI_STREAM_H266 == pes->codecid) + { + return mpeg_packet_h26x(pkt, pes, total, consume, handler, param); + } + else + { + // save pts/dts + pkt->pts = pes->pts; + pkt->dts = pes->dts; + pkt->sid = pes->sid; + pkt->codecid = pes->codecid; + pkt->flags = pes->flags; + + // for audio packet only, H.264/H.265 pes->len maybe incorrect + assert(PSI_STREAM_H264 != pes->codecid && PSI_STREAM_H265 != pes->codecid && PSI_STREAM_H266 != pes->codecid); +#if !defined(MPEG_LIVING_VIDEO_FRAME_DEMUX) + if (PES_SID_VIDEO != pes->sid) +#endif + if (pes->len > 0 && pes->pkt.size >= pes->len) + { + pes_packet_codec_verify(pes, pkt); // verify on packet complete + if (PSI_STREAM_H264 == pes->codecid || PSI_STREAM_H265 == pes->codecid || PSI_STREAM_H266 == pes->codecid) + return mpeg_packet_h26x(pkt, pes, size, consume, handler, param); + + assert(pes->pkt.size == pes->len || (pkt->flags & MPEG_FLAG_PACKET_CORRUPT)); // packet lost + r = handler(param, pes->pn, pes->pid, pkt->codecid, pkt->flags, pkt->pts, pkt->dts, pes->pkt.data, pes->len); + pkt->size = 0; // new packet start + } + } + + return r; +} diff --git a/MediaServer/libmpeg/source/mpeg-pat.c b/MediaServer/libmpeg/source/mpeg-pat.c new file mode 100644 index 0000000..a86dd6e --- /dev/null +++ b/MediaServer/libmpeg/source/mpeg-pat.c @@ -0,0 +1,204 @@ +// ITU-T H.222.0(10/2014) +// Information technology - Generic coding of moving pictures and associated audio information: Systems +// 2.4.4.3 Program association table(p65) + +#include "mpeg-ts-internal.h" +#include +#include +#include + +struct pmt_t* pat_alloc_pmt(struct pat_t* pat) +{ + void* ptr; + unsigned int n; + + if (NULL == pat->pmts) + { + assert(0 == pat->pmt_count); + assert(0 == pat->pmt_capacity); + pat->pmts = pat->pmt_default; + pat->pmt_capacity = sizeof(pat->pmt_default) / sizeof(pat->pmt_default[0]); + } + + if (pat->pmt_count >= pat->pmt_capacity) + { + if (pat->pmt_count + 1 > 65535) + { + assert(0); + return NULL; + } + + n = pat->pmt_capacity + pat->pmt_capacity / 4 + 4; + ptr = realloc(pat->pmts == pat->pmt_default ? NULL : pat->pmts, sizeof(pat->pmts[0]) * n); + if (!ptr) + return NULL; + + assert(ptr != pat->pmt_default); + if (pat->pmts == pat->pmt_default) + memmove(ptr, pat->pmt_default, sizeof(pat->pmt_default)); + pat->pmts = (struct pmt_t*)ptr; + pat->pmt_capacity = n; + } + + // new pmt + memset(&pat->pmts[pat->pmt_count], 0, sizeof(pat->pmts[0])); + return &pat->pmts[pat->pmt_count]; +} + +static struct pmt_t* pat_fetch(struct pat_t* pat, uint16_t pid) +{ + unsigned int i; + struct pmt_t* pmt; + for(i = 0; i < pat->pmt_count; i++) + { + if(pat->pmts[i].pid == pid) + return &pat->pmts[i]; + } + + // new pmt + pmt = pat_alloc_pmt(pat); + pat->pmt_count++; + return pmt; +} + +size_t pat_read(struct pat_t *pat, const uint8_t* data, size_t bytes) +{ + // Table 2-30 Program association section(p65) + + struct pmt_t* pmt; + uint32_t i = 0; + uint16_t pn, pid; +// uint32_t crc = 0; + uint32_t section_length, transport_stream_id, version_number; + + if(bytes < 8) + return 0; // invalid data length +// printf("PAT: %0x %0x %0x %0x %0x %0x %0x %0x\n", (unsigned int)data[0], (unsigned int)data[1], (unsigned int)data[2], (unsigned int)data[3], (unsigned int)data[4], (unsigned int)data[5], (unsigned int)data[6], (unsigned int)data[7]); + assert(PAT_TID_PAS == data[0]); // table_id + assert(1 == ((data[1] >> 7) & 0x01)); // section_syntax_indicator +// uint32_t zero = (data[1] >> 6) & 0x01; +// uint32_t reserved = (data[1] >> 4) & 0x03; + section_length = ((data[1] & 0x0F) << 8) | data[2]; + transport_stream_id = (data[3] << 8) | data[4]; +// uint32_t reserved2 = (data[5] >> 6) & 0x03; + version_number = (data[5] >> 1) & 0x1F; +// uint32_t current_next_indicator = data[5] & 0x01; +// uint32_t sector_number = data[6]; +// uint32_t last_sector_number = data[7]; + + if (PAT_TID_PAS != data[0] || section_length + 3 < 8 + 4 /*crc32*/ || section_length + 3 > bytes) + { + assert(0); + return 0; // invalid data length + } + + assert(bytes >= section_length + 3); // PMT = section_length + 3 + //if(pat->ver != version_number) + // pat_clear(pat); // fix pat.pmt[i].pes.pkt.data memory leak + pat->tsid = transport_stream_id; + pat->ver = version_number; + + // TODO: version_number change, reload pmts + + // 4:CRC, 5:follow section_length item + for(i = 8; i + 4 <= section_length + 8 - 5 - 4/*CRC32*/ && section_length + 3 <= bytes; i += 4) + { + pn = (data[i] << 8) | data[i+1]; + pid = ((data[i+2] & 0x1F) << 8) | data[i+3]; +// printf("PAT: pn: 0x%0x, pid: 0x%0x\n", (unsigned int)pn, (unsigned int)pid); + + if(0 == pn) + continue; // ignore NIT info + pmt = pat_fetch(pat, pid); + if(NULL == pmt) + continue; + + pmt->pn = pn; + pmt->pid = pid; + } + + //assert(i+4 == bytes); + //crc = (data[i] << 24) | (data[i+1] << 16) | (data[i+2] << 8) | data[i+3]; + //crc = mpeg_crc32(-1, data, bytes-4); +// assert(0 == mpeg_crc32(0xffffffff, data, section_length+3)); + return section_length + 3; +} + +size_t pat_write(const struct pat_t *pat, uint8_t *data) +{ + // Table 2-30 Program association section(p65) + + uint32_t i = 0; + uint32_t len = 0; + uint32_t crc = 0; + + len = pat->pmt_count * 4 + 5 + 4; // 5 bytes remain header and 4 bytes crc32 + + // shall not exceed 1021 (0x3FD). + assert(len <= 1021); + assert(len <= TS_PACKET_SIZE - 7); + + data[0] = PAT_TID_PAS; // program association table + + // section_syntax_indicator = '1' + // '0' + // reserved '11' + nbo_w16(data + 1, (uint16_t)(0xb000 | len)); + + // transport_stream_id + nbo_w16(data + 3, (uint16_t)pat->tsid); + + // reserved '11' + // version_number 'xxxxx' + // current_next_indicator '1' + data[5] = (uint8_t)(0xC1 | (pat->ver << 1)); + + // section_number/last_section_number + data[6] = 0x00; + data[7] = 0x00; + + for(i = 0; i < pat->pmt_count; i++) + { + nbo_w16(data + 8 + i * 4 + 0, (uint16_t)pat->pmts[i].pn); + nbo_w16(data + 8 + i * 4 + 2, (uint16_t)(0xE000 | pat->pmts[i].pid)); + } + + // crc32 + crc = mpeg_crc32(0xffffffff, data, len-1); + //put32(data + section_length - 1, crc); + data[len - 1 + 3] = (crc >> 24) & 0xFF; + data[len - 1 + 2] = (crc >> 16) & 0xFF; + data[len - 1 + 1] = (crc >> 8) & 0xFF; + data[len - 1 + 0] = crc & 0xFF; + + return len + 3; // total length +} + +struct pmt_t* pat_find(struct pat_t* pat, uint16_t pn) +{ + unsigned int i; + for(i = 0; i < pat->pmt_count; i++) + { + if(pat->pmts[i].pn == pn) + return &pat->pmts[i]; + } + return NULL; +} + +void pat_clear(struct pat_t* pat) +{ + unsigned int i; + for (i = 0; i < pat->pmt_count; i++) + { + pmt_clear(&pat->pmts[i]); + } + + if (pat->pmts && pat->pmts != pat->pmt_default) + { + free(pat->pmts); + pat->pmts = NULL; + } + + pat->pmt_count = 0; + pat->pmt_capacity = 0; +} diff --git a/MediaServer/libmpeg/source/mpeg-pes-internal.h b/MediaServer/libmpeg/source/mpeg-pes-internal.h new file mode 100644 index 0000000..fb991d4 --- /dev/null +++ b/MediaServer/libmpeg/source/mpeg-pes-internal.h @@ -0,0 +1,99 @@ +#ifndef _mpeg_pes_internal_h_ +#define _mpeg_pes_internal_h_ + +#include "mpeg-proto.h" +#include "mpeg-types.h" +#include "mpeg-util.h" + +enum { + MPEG_ERROR_NEED_MORE_DATA = 0, + MPEG_ERROR_OK, + MPEG_ERROR_INVALID_DATA, +}; + +struct packet_t +{ + uint8_t sid; + uint8_t codecid; + + int flags; + int64_t pts; + int64_t dts; + uint8_t *data; + size_t size; + size_t capacity; + + int vcl; // h.264/h.265 only +}; + +struct pes_t +{ + uint16_t pn; // TS program number(0-ps) + uint16_t pid; // PES PID : 13 + uint8_t sid; // PES stream_id : 8 + uint8_t codecid; // PMT/PSM stream_type : 8 + uint8_t cc; // continuity_counter : 4; + uint8_t* esinfo; // es_info + uint16_t esinfo_len;// es_info_length : 12 + + uint32_t len; // PES_packet_length : 16; + + uint32_t reserved10 : 2; + uint32_t PES_scrambling_control : 2; + uint32_t PES_priority : 1; + uint32_t data_alignment_indicator : 1; + uint32_t copyright : 1; + uint32_t original_or_copy : 1; + + uint32_t PTS_DTS_flags : 2; + uint32_t ESCR_flag : 1; + uint32_t ES_rate_flag : 1; + uint32_t DSM_trick_mode_flag : 1; + uint32_t additional_copy_info_flag : 1; + uint32_t PES_CRC_flag : 1; + uint32_t PES_extension_flag : 1; + uint32_t PES_header_data_length : 8; + + int64_t pts; + int64_t dts; + int64_t ESCR_base; + uint32_t ESCR_extension; + uint32_t ES_rate; + + //uint8_t trick_mode; + //uint32_t trick_mode_control : 3; + //uint32_t field_id : 2; + //uint32_t intra_slice_refresh : 1; + //uint32_t frequency_truncation : 2; + + //uint8_t additional_copy_info; + //int16_t previous_PES_packet_CRC; + + //uint32_t PES_private_data_flag : 1; + //uint32_t pack_header_field_flag : 1; + //uint32_t program_packet_sequence_counter_flag : 1; + //uint32_t P_STD_buffer_flag : 1; + //uint32_t reserved_ : 3; + //uint32_t PES_extension_flag_2 : 1; + //uint32_t PES_private_data_flag2 : 1; + //uint8_t PES_private_data[128/8]; + + //uint32_t pack_field_length : 8; + + int have_pes_header; // TS demuxer only + int flags; // TS/PS demuxer only + struct packet_t pkt; +}; + +int pes_read_header(struct pes_t *pes, struct mpeg_bits_t* reader); +int pes_read_mpeg1_header(struct pes_t* pes, struct mpeg_bits_t* reader); +size_t pes_write_header(const struct pes_t *pes, uint8_t* data, size_t bytes); + +typedef int (*pes_packet_handler)(void* param, int program, int stream, int codecid, int flags, int64_t pts, int64_t dts, const void* data, size_t bytes); +int pes_packet(struct packet_t* pkt, struct pes_t* pes, const void* data, size_t size, size_t* consume, int start, pes_packet_handler handler, void* param); + +uint16_t mpeg_bits_read15(struct mpeg_bits_t* reader); +uint32_t mpeg_bits_read30(struct mpeg_bits_t* reader); +uint64_t mpeg_bits_read45(struct mpeg_bits_t* reader); + +#endif /* !_mpeg_pes_internal_h_ */ diff --git a/MediaServer/libmpeg/source/mpeg-pes.c b/MediaServer/libmpeg/source/mpeg-pes.c new file mode 100644 index 0000000..f207420 --- /dev/null +++ b/MediaServer/libmpeg/source/mpeg-pes.c @@ -0,0 +1,323 @@ +// ITU-T H.222.0(10/2014) +// Information technology - Generic coding of moving pictures and associated audio information: Systems +// 2.4.3.6 PES packet(p51) + +#include "mpeg-pes-internal.h" +#include +#include +#include + +/// @return 0-error, other-pes header length +int pes_read_header(struct pes_t *pes, struct mpeg_bits_t* reader) +{ + uint8_t v8; + uint16_t v16; + uint32_t v32; + size_t end; + + //pes->sid = mpeg_bits_read8(reader); + pes->len = mpeg_bits_read16(reader); + + v8 = mpeg_bits_read8(reader); + //assert(0x02 == ((v8 >> 6) & 0x3)); + pes->PES_scrambling_control = (v8 >> 4) & 0x3; + pes->PES_priority = (v8 >> 3) & 0x1; + pes->data_alignment_indicator = (v8 >> 2) & 0x1; + pes->copyright = (v8 >> 1) & 0x1; + pes->original_or_copy = v8 & 0x1; + + v8 = mpeg_bits_read8(reader); + pes->PTS_DTS_flags = (v8 >> 6) & 0x3; + pes->ESCR_flag = (v8 >> 5) & 0x1; + pes->ES_rate_flag = (v8 >> 4) & 0x1; + pes->DSM_trick_mode_flag = (v8 >> 3) & 0x1; + pes->additional_copy_info_flag = (v8 >> 2) & 0x1; + pes->PES_CRC_flag = (v8 >> 1) & 0x1; + pes->PES_extension_flag = v8 & 0x1; + + pes->PES_header_data_length = mpeg_bits_read8(reader); + if (pes->len > 0 && pes->len < pes->PES_header_data_length + 3) + return MPEG_ERROR_INVALID_DATA; // skip invalid packet + + end = mpeg_bits_tell(reader) + pes->PES_header_data_length; + if (mpeg_bits_error(reader) || end > mpeg_bits_length(reader)) + return MPEG_ERROR_NEED_MORE_DATA; + + if (0x02 & pes->PTS_DTS_flags) + { + v8 = mpeg_bits_read8(reader); + //assert(0x20 == (v8 & 0x20)); + pes->pts = ((((uint64_t)v8 >> 1) & 0x07) << 30) | mpeg_bits_read30(reader); + } + //else + //{ + // pes->pts = PTS_NO_VALUE; + //} + + if (0x01 & pes->PTS_DTS_flags) + { + v8 = mpeg_bits_read8(reader); + //assert(0x10 == (v8 & 0x10)); + pes->dts = ((((uint64_t)v8 >> 1) & 0x07) << 30) | mpeg_bits_read30(reader); + } + else if(0x02 & pes->PTS_DTS_flags) + { + // has pts + pes->dts = pes->pts; + } + //else + //{ + // pes->dts = PTS_NO_VALUE; + //} + + if (pes->ESCR_flag) + { + v32 = mpeg_bits_read32(reader); + v16 = mpeg_bits_read16(reader); + pes->ESCR_base = (((uint64_t)((v32 >> 27) & 0x07)) << 30) | (((uint64_t)((v32 >> 11) & 0x7FFF)) << 15) | (((uint64_t)(v32 & 0x3FF)) << 5) | ((uint64_t)(v16 >> 11) & 0x1F); + pes->ESCR_extension = (v16 >> 1) & 0x1FF; + } + + if (pes->ES_rate_flag) + { + pes->ES_rate = (mpeg_bits_read8(reader) & 0x7F) << 15; + pes->ES_rate |= mpeg_bits_read15(reader); + } + + if (pes->DSM_trick_mode_flag) + { + // TODO: + //mpeg_bits_skip(reader, 1); + } + + if (pes->additional_copy_info_flag) + { + //mpeg_bits_skip(reader, 1); + } + + if (pes->PES_CRC_flag) + { + //mpeg_bits_skip(reader, 2); + } + + if (pes->PES_extension_flag) + { + } + + if (pes->len > 0) + { + if (pes->len < pes->PES_header_data_length + 3) + return MPEG_ERROR_INVALID_DATA; // skip invalid packet + pes->len -= pes->PES_header_data_length + 3; + } + + assert(pes->len >= 0); // TS pes->len maybe 0(payload > 65535) + mpeg_bits_seek(reader, end); + assert(0 == mpeg_bits_error(reader)); + return mpeg_bits_error(reader) ? MPEG_ERROR_INVALID_DATA : MPEG_ERROR_OK; +} + +/// @return 0-error, pes header length +size_t pes_write_header(const struct pes_t *pes, uint8_t* data, size_t bytes) +{ + uint8_t len = 0; + uint8_t flags = 0x00; + uint8_t *p = NULL; + + if (bytes < 9) return 0; // error + + // packet_start_code_prefix 0x000001 + data[0] = 0x00; + data[1] = 0x00; + data[2] = 0x01; + data[3] = pes->sid; + + // skip PES_packet_length + //data[4] = 0x00; + //data[5] = 0x00; + + // '10' + // PES_scrambling_control '00' + // PES_priority '0' + // data_alignment_indicator '1' + // copyright '0' + // original_or_copy '0' + data[6] = 0x80; + if(pes->data_alignment_indicator) + data[6] |= 0x04; + //if (IDR | subtitle | raw data) + //data[6] |= 0x04; + + // PTS_DTS_flag 'xx' + // ESCR_flag '0' + // ES_rate_flag '0' + // DSM_trick_mode_flag '0' + // additional_copy_info_flag '0' + // PES_CRC_flag '0' + // PES_extension_flag '0' + if(PTS_NO_VALUE != pes->pts) + { + flags |= 0x80; // pts + len += 5; + } + assert(PTS_NO_VALUE == pes->dts || pes->pts == pes->dts || PES_SID_VIDEO == data[3]); // audio PTS==DTS + if(PTS_NO_VALUE != pes->dts /*&& PES_SID_VIDEO==(PES_SID_VIDEO&data[3])*/ && pes->dts != pes->pts) + { + flags |= 0x40; // dts + len += 5; + } + data[7] = flags; + + // PES_header_data_length : 8 + data[8] = len; + + if ((size_t)len + 9 > bytes) + return 0; // error + p = data + 9; + + if(flags & 0x80) + { + *p++ = ((flags >> 2) & 0x30)/* 0011/0010 */ | (((pes->pts >> 30) & 0x07) << 1) /* PTS 30-32 */ | 0x01 /* marker_bit */; + *p++ = (pes->pts >> 22) & 0xFF; /* PTS 22-29 */ + *p++ = ((pes->pts >> 14) & 0xFE) /* PTS 15-21 */ | 0x01 /* marker_bit */; + *p++ = (pes->pts >> 7) & 0xFF; /* PTS 7-14 */ + *p++ = ((pes->pts << 1) & 0xFE) /* PTS 0-6 */ | 0x01 /* marker_bit */; + } + + if(flags & 0x40) + { + *p++ = 0x10 /* 0001 */ | (((pes->dts >> 30) & 0x07) << 1) /* DTS 30-32 */ | 0x01 /* marker_bit */; + *p++ = (pes->dts >> 22) & 0xFF; /* DTS 22-29 */ + *p++ = ((pes->dts >> 14) & 0xFE) /* DTS 15-21 */ | 0x01 /* marker_bit */; + *p++ = (pes->dts >> 7) & 0xFF; /* DTS 7-14 */ + *p++ = ((pes->dts << 1) & 0xFE) /* DTS 0-6 */ | 0x01 /* marker_bit */; + } + + return p - data; +} + +// ISO/IEC 11172-1 +// 2.4.3.3 Packet Layer (p20) +/* +packet() { + packet_start_code_prefix 24 bslbf + stream_id 8 uimsbf + packet_length 16 uimsbf + if (packet_start_code != private_stream_2) { + while (nextbits() == '1') + stuffing_byte 8 bslbf + + if (nextbits () == '01') { + '01' 2 bslbf + STD_buffer_scale 1 bslbf + STD_buffer_size 13 uimsbf + } + if (nextbits() == '0010') { + '0010' 4 bslbf + presentation_time_stamp[32..30] 3 bslbf + marker_bit 1 bslbf + presentation_time_stamp[29..15] 15 bslbf + marker_bit 1 bslbf + presentation_time_stamp[14..0] 15 bslbf + marker_bit 1 bslbf + } + else if (nextbits() == '0011') { + '0011' 4 bslbf + presentation_time_stamp[32..30] 3 bslbf + marker_bit 1 bslbf + presentation_time_stamp[29..15] 15 bslbf + marker_bit 1 bslbf + presentation_time_stamp[14..0] 15 bslbf + marker_bit 1 bslbf + '0001' 4 bslbf + decoding_time_stamp[32..30] 3 bslbf + marker_bit 1 bslbf + decoding_time_stamp[29..15] 15 bslbf + marker_bit 1 bslbf + decoding_time_stamp[14..0] 15 bslbf + marker_bit 1 bslbf + } + else + '0000 1111' 8 bslbf + } + + for (i = 0; i < N; i++) { + packet_data_byte 8 bslbf + } +} +*/ +int pes_read_mpeg1_header(struct pes_t *pes, struct mpeg_bits_t* reader) +{ + uint8_t v8; + size_t offset; + + //pes->sid = mpeg_bits_read8(reader); + pes->len = mpeg_bits_read16(reader); + offset = mpeg_bits_tell(reader); + + do + { + v8 = mpeg_bits_read8(reader); + } while (0 == mpeg_bits_error(reader) && v8 == 0xFF); + + if (0x40 == (0xC0 & v8)) + { + mpeg_bits_skip(reader, 2); // skip STD_buffer_scale / STD_buffer_size + v8 = mpeg_bits_read8(reader); + } + + if (0x20 == (0xF0 & v8)) + { + pes->pts = ((((uint64_t)v8 >> 1) & 0x07) << 30) | mpeg_bits_read30(reader); + } + else if (0x30 == (0xF0 & v8)) + { + pes->pts = ((((uint64_t)v8 >> 1) & 0x07) << 30) | mpeg_bits_read30(reader); + + v8 = mpeg_bits_read8(reader); + pes->dts = ((((uint64_t)v8 >> 1) & 0x07) << 30) | mpeg_bits_read30(reader); + } + else + { + assert(0x0F == v8); + } + + if (mpeg_bits_error(reader)) + return MPEG_ERROR_NEED_MORE_DATA; + + offset = mpeg_bits_tell(reader) - offset; + if (pes->len > 0) + { + if (pes->len < offset) + return MPEG_ERROR_INVALID_DATA; // invalid data length + pes->len -= (uint32_t)offset; + } + + assert(0 == mpeg_bits_error(reader)); + return MPEG_ERROR_OK; +} + +uint16_t mpeg_bits_read15(struct mpeg_bits_t* reader) +{ + uint16_t v; + v = ((uint16_t)mpeg_bits_read8(reader)) << 7; + v |= (mpeg_bits_read8(reader) >> 1) & 0x7F; + return v; +} + +uint32_t mpeg_bits_read30(struct mpeg_bits_t* reader) +{ + uint32_t v; + v = ((uint32_t)mpeg_bits_read15(reader)) << 15; + v |= mpeg_bits_read15(reader); + return v; +} + +uint64_t mpeg_bits_read45(struct mpeg_bits_t* reader) +{ + uint64_t v; + v = ((uint64_t)mpeg_bits_read15(reader)) << 30; + v |= ((uint64_t)mpeg_bits_read15(reader)) << 15; + v |= mpeg_bits_read15(reader); + return v; +} diff --git a/MediaServer/libmpeg/source/mpeg-pmt.c b/MediaServer/libmpeg/source/mpeg-pmt.c new file mode 100644 index 0000000..89be817 --- /dev/null +++ b/MediaServer/libmpeg/source/mpeg-pmt.c @@ -0,0 +1,354 @@ +// ITU-T H.222.0(10/2014) +// Information technology - Generic coding of moving pictures and associated audio information: Systems +// 2.4.4.8 Program map table(p68) + +#include "mpeg-ts-internal.h" +#include "mpeg-ts-opus.h" +#include "mpeg-util.h" +#include +#include +#include + +static struct pes_t* pmt_fetch(struct pmt_t* pmt, uint16_t pid) +{ + unsigned int i; + for(i = 0; i < pmt->stream_count; i++) + { + if(pmt->streams[i].pid == pid) + return &pmt->streams[i]; + } + + if(pmt->stream_count >= sizeof(pmt->streams) / sizeof(pmt->streams[0])) + { + assert(0); + return NULL; + } + + // new stream + return &pmt->streams[pmt->stream_count++]; +} + +static int pmt_read_program_descriptor(struct pmt_t* pmt, const uint8_t* data, uint16_t bytes) +{ + uint8_t tag; + uint8_t len; + uint8_t channels; + + // Registration descriptor + while (bytes > 2) + { + tag = data[0]; + len = data[1]; + if (len + 2 > bytes) + return -1; // invalid len + + // ISO/IEC 13818-1:2018 (E) Table 2-45 Program and program element descriptors (p90) + switch (tag) + { + case 0x05: // 2.6.8 Registration descriptor(p94) + if (len >= 4 && 'C' == data[2] && 'U' == data[3] && 'E' == data[4] && 'I' == data[5]) + { + memcpy(pmt->proginfo, data+2, 4); + } + break; + } + + data += len + 2; + bytes -= len + 2; + } + assert(0 == bytes); + + return 0; +} + +static int pmt_read_descriptor(struct pes_t* stream, const uint8_t* data, uint16_t bytes) +{ + uint8_t tag; + uint8_t len; + uint8_t channels; + + // Registration descriptor + while (bytes > 2) + { + tag = data[0]; + len = data[1]; + if (len + 2 > bytes) + return -1; // invalid len + + // ISO/IEC 13818-1:2018 (E) Table 2-45 ?Program and program element descriptors (p90) + switch (tag) + { + case 0x05: // 2.6.8 Registration descriptor(p94) + if (len >= 4 && 'O' == data[2] && 'p' == data[3] && 'u' == data[4] && 's' == data[5]) + { + assert(PSI_STREAM_PRIVATE_DATA == stream->codecid); + stream->codecid = PSI_STREAM_AUDIO_OPUS; + } + else if (len >= 4 && 'A' == data[2] && 'V' == data[3] && '0' == data[4] && '1' == data[5]) + { + // https://aomediacodec.github.io/av1-mpeg2-ts/ + // Constraints on AV1 streams in MPEG-2 TS + assert(PSI_STREAM_PRIVATE_DATA == stream->codecid); + stream->codecid = PSI_STREAM_AV1; + } + else if (len >= 4 && 'A' == data[2] && 'V' == data[3] && 'S' == data[4] && '3' == data[5]) + { + stream->codecid = PSI_STREAM_VIDEO_AVS3; + } + break; + + case 0x7f: // DVB-Service Information: 6.1 Descriptor identification and location (p38) + // 2.6.90 Extension descriptor + if (len >= 2 && data[3] <= sizeof(opus_channel_map)/sizeof(opus_channel_map[0]) && PSI_STREAM_AUDIO_OPUS == stream->codecid && OPUS_EXTENSION_DESCRIPTOR_TAG == data[2]) // User defined (provisional Opus) + { + channels = data[3]; + stream->esinfo = (uint8_t*)calloc(1, sizeof(opus_default_extradata)); + if (stream->esinfo) + { + stream->esinfo_len = sizeof(opus_default_extradata); + memcpy(stream->esinfo, opus_default_extradata, stream->esinfo_len); + stream->esinfo[9] = channels ? channels : 2; + stream->esinfo[18] = channels ? (channels > 2 ? 1 : 0) : /* Dual Mono */ 255; + stream->esinfo[19] = opus_stream_cnt[channels]; + stream->esinfo[20] = opus_coupled_stream_cnt[channels]; + memcpy(stream->esinfo + 21, opus_channel_map[(channels ? channels : 2) - 1], channels ? channels : 2); + } + } + } + + data += len + 2; + bytes -= len + 2; + } + assert(0 == bytes); + + return 0; +} + +static int pmt_write_descriptor(const struct pes_t* stream, uint8_t* data, int bytes) +{ + uint8_t* p; + + p = data; + if (PSI_STREAM_AUDIO_OPUS == stream->codecid && bytes > 2 + 4 /*fourcc*/ + 4 /*DVI OPUS*/ ) + { + *p++ = 0x05; // 2.6.8 Registration descriptor(p94) + *p++ = 4; + memcpy(p, "Opus", 4); + p += 4; + + *p++ = 0x7f; // DVB-Service Information: 6.1 Descriptor identification and location (p38) + *p++ = 2; + *p++ = OPUS_EXTENSION_DESCRIPTOR_TAG; + *p++ = stream->esinfo_len > 8 ? stream->esinfo[9] : 2 /*0xFF*/ ; // default 2-channels + } + + return (int)(p - data); +} + +size_t pmt_read(struct pmt_t *pmt, const uint8_t* data, size_t bytes) +{ + struct pes_t* stream; + uint16_t pid, len; + uint32_t i, section_length, program_number, version_number; + uint32_t PCR_PID, program_info_length; + + if (bytes < 12) + return 0; // invalid data length +// printf("PMT: %0x %0x %0x %0x %0x %0x %0x %0x, %0x, %0x, %0x, %0x\n", (unsigned int)data[0], (unsigned int)data[1], (unsigned int)data[2], (unsigned int)data[3], (unsigned int)data[4], (unsigned int)data[5], (unsigned int)data[6],(unsigned int)data[7],(unsigned int)data[8],(unsigned int)data[9],(unsigned int)data[10],(unsigned int)data[11]); + assert(PAT_TID_PMS == data[0]); + assert(1 == ((data[1] >> 7) & 0x01)); + section_length = ((data[1] & 0x0F) << 8) | data[2]; + program_number = (data[3] << 8) | data[4]; +// uint32_t reserved2 = (data[5] >> 6) & 0x03; + version_number = (data[5] >> 1) & 0x1F; +// uint32_t current_next_indicator = data[5] & 0x01; + assert(0 == data[6]); // sector_number + assert(0 == data[7]); // last_sector_number +// uint32_t reserved3 = (data[8] >> 5) & 0x07; + PCR_PID = ((data[8] & 0x1F) << 8) | data[9]; +// uint32_t reserved4 = (data[10] >> 4) & 0x0F; + program_info_length = ((data[10] & 0x0F) << 8) | data[11]; + + if (PAT_TID_PMS != data[0] || section_length + 3 < 12 + 4 /*crc32*/ || section_length + 3 > bytes + || program_info_length + 12 /*head*/ > section_length + 3 /*head*/ - 4 /*crc32*/ ) + { + assert(0); + return 0; // invalid data length + } + + //if(pmt->ver != version_number) + // pmt_clear(pmt); // fix pmt.pes.pkt.data memory leak + + pmt->PCR_PID = PCR_PID; + pmt->pn = program_number; + //pmt->ver = version_number; + pmt->pminfo_len = program_info_length; + + if(program_info_length > 2) + { + // descriptor(data + 12, program_info_length) + pmt_read_program_descriptor(pmt, data + 12, program_info_length); + } + +PMT_VERSION_CHANGE: + assert(bytes >= section_length + 3); // PMT = section_length + 3 + for (i = 12 + program_info_length; i + 5 <= section_length + 3 - 4/*CRC32*/ && section_length + 3 <= bytes; i += len + 5) // 9: follow section_length item + { + pid = ((data[i+1] & 0x1F) << 8) | data[i+2]; + len = ((data[i+3] & 0x0F) << 8) | data[i+4]; + //printf("PMT: pn: 0x%0x, pid: 0x%0x, codec: 0x%0x, eslen: %d\n", (unsigned int)pmt->pn, (unsigned int)pid, (unsigned int)data[i], (unsigned int)len); + + if (i + len + 5 > section_length + 3 - 4/*CRC32*/) + break; // mark error ? + + assert(pmt->stream_count <= sizeof(pmt->streams)/sizeof(pmt->streams[0])); + stream = pmt_fetch(pmt, pid); + if (NULL == stream) + { + if (pmt->ver != version_number) + { + pmt->ver = version_number; // once only + pmt_clear(pmt); + goto PMT_VERSION_CHANGE; + } + continue; + } + + stream->pn = (uint16_t)pmt->pn; + stream->pid = pid; + stream->codecid = data[i]; + stream->esinfo_len = 0; // default nothing + if (len > 2) + { + //descriptor(data + i + 5, len) + pmt_read_descriptor(stream, data + i + 5, len); + } + } + + pmt->ver = version_number; + //assert(j+4 == bytes); + //crc = (data[j] << 24) | (data[j+1] << 16) | (data[j+2] << 8) | data[j+3]; +// assert(0 == mpeg_crc32(0xffffffff, data, section_length+3)); + return section_length + 3; +} + +size_t pmt_write(const struct pmt_t *pmt, uint8_t *data) +{ + // 2.4.4.8 Program map table (p68) + // Table 2-33 + + uint32_t i = 0; + uint32_t crc = 0; + ptrdiff_t len = 0; + uint8_t *p = NULL; + + data[0] = PAT_TID_PMS; // program map table + + // skip section_length + + // program_number + nbo_w16(data + 3, (uint16_t)pmt->pn); + + // reserved '11' + // version_number 'xxxxx' + // current_next_indicator '1' + data[5] = (uint8_t)(0xC1 | (pmt->ver << 1)); + + // section_number/last_section_number + data[6] = 0x00; + data[7] = 0x00; + + // reserved '111' + // PCR_PID 13-bits 0x1FFF + nbo_w16(data + 8, (uint16_t)(0xE000 | pmt->PCR_PID)); + + // reserved '1111' + // program_info_length 12-bits, the first two bits of which shall be '00'. + assert(pmt->pminfo_len < 0x400); + nbo_w16(data + 10, (uint16_t)(0xF000 | pmt->pminfo_len)); + if(pmt->pminfo_len > 0 && pmt->pminfo_len < 0x400) + { + // fill program info + assert(pmt->pminfo); + memcpy(data + 12, pmt->pminfo, pmt->pminfo_len); + } + + // streams + p = data + 12 + pmt->pminfo_len; + for(i = 0; i < pmt->stream_count && p - data < 1021 - 4 - 5 - pmt->streams[i].esinfo_len; i++) + { + // stream_type + *p = (uint8_t)(PSI_STREAM_AUDIO_OPUS == pmt->streams[i].codecid) ? PSI_STREAM_PRIVATE_DATA : pmt->streams[i].codecid; + + // reserved '111' + // elementary_PID 13-bits + nbo_w16(p + 1, 0xE000 | pmt->streams[i].pid); + + len = 0; + // fill elementary stream info + //if(PSI_STREAM_AUDIO_OPUS == pmt->streams[i].codecid || (pmt->streams[i].esinfo_len > 0 && pmt->streams[i].esinfo)) + { + //assert(pmt->streams[i].esinfo); + //memcpy(p, pmt->streams[i].esinfo, pmt->streams[i].esinfo_len); + //p += pmt->streams[i].esinfo_len; + len = pmt_write_descriptor(&pmt->streams[i], p + 5, 1021 - (int)(p + 5 - data)); + } + + // reserved '1111' + // ES_info_lengt 12-bits + nbo_w16(p + 3, 0xF000 | (uint16_t)len); + p += 5 + len; + } + + // section_length + len = p + 4 - (data + 3); // 4 bytes crc32 + assert(len <= 1021); // shall not exceed 1021 (0x3FD). + assert(len <= TS_PACKET_SIZE - 7); + // section_syntax_indicator '1' + // '0' + // reserved '11' + nbo_w16(data + 1, (uint16_t)(0xb000 | len)); + + // crc32 + crc = mpeg_crc32(0xffffffff, data, (uint32_t)(p-data)); + //put32(p, crc); + p[3] = (crc >> 24) & 0xFF; + p[2] = (crc >> 16) & 0xFF; + p[1] = (crc >> 8) & 0xFF; + p[0] = crc & 0xFF; + + return (p - data) + 4; // total length +} + +void pmt_clear(struct pmt_t* pmt) +{ + unsigned int i; + struct pes_t* pes; + + for (i = 0; i < pmt->stream_count; i++) + { + pes = &pmt->streams[i]; + if (pes->pkt.data) + { + free(pes->pkt.data); + pes->pkt.data = NULL; + } + pes->pkt.size = 0; + + if (pes->esinfo) + { + free(pes->esinfo); + pes->esinfo = NULL; + } + pes->esinfo_len = 0; + } + pmt->stream_count = 0; + + if (pmt->pminfo) + { + free(pmt->pminfo); + pmt->pminfo = NULL; + + } + pmt->pminfo_len = 0; +} diff --git a/MediaServer/libmpeg/source/mpeg-ps-dec.c b/MediaServer/libmpeg/source/mpeg-ps-dec.c new file mode 100644 index 0000000..8cbca05 --- /dev/null +++ b/MediaServer/libmpeg/source/mpeg-ps-dec.c @@ -0,0 +1,519 @@ +// ITU-T H.222.0(06/2012) +// Information technology - Generic coding of moving pictures and associated audio information: Systems +// 2.5.3.1 Program stream(p74) + +#include +#include +#include "mpeg-ps.h" +#include "mpeg-ps-internal.h" +#include "mpeg-pes-internal.h" +#include "mpeg-util.h" +#include +#include +#include +#include + +#define N_BUFFER_INIT 256 +#define N_BUFFER_MAX (512*1024) +#define N_BUFFER_INC (8000) + +enum ps_demuxer_state_t +{ + PS_DEMUXER_STATE_START = 0, + PS_DEMUXER_STATE_DATA, +}; + +struct ps_demuxer_t +{ + struct psm_t psm; + struct psd_t psd; + + struct ps_pack_header_t pkhd; + struct ps_system_header_t system; + + enum ps_demuxer_state_t state; + struct pes_t* pes; + size_t pes_length; + struct + { + uint8_t* ptr; + size_t len, cap; + } buffer; + + int start; + int sync; + + ps_demuxer_onpacket onpacket; + void* param; + + struct ps_demuxer_notify_t notify; + void* notify_param; + uint32_t ver; // psm notify version +}; + +static void ps_demuxer_notify(struct ps_demuxer_t* ps); + +static int ps_demuxer_find_startcode(struct mpeg_bits_t *reader) +{ + uint8_t v8; + size_t zeros; + + zeros = 0; + while (1) + { + v8 = mpeg_bits_read8(reader); + if (0 != mpeg_bits_error(reader)) + break; + + if (0x01 == v8 && zeros >= 2) + return 0; + + zeros = 0x00 != v8 ? 0 : (zeros + 1); + } + + return -1; +} + +#if defined(MPEG_ZERO_PAYLOAD_LENGTH) +static int ps_demuxer_find_pes_start(struct mpeg_bits_t* reader) +{ + size_t origin, offset; + origin = mpeg_bits_tell(reader); + while (0 == mpeg_bits_error(reader)) + { + if (0 != ps_demuxer_find_startcode(reader)) + break; + + offset = mpeg_bits_tell(reader); + if (PES_SID_START == mpeg_bits_read8(reader) && 0 == mpeg_bits_error(reader)) + { + mpeg_bits_seek(reader, origin); + return (int)(offset - origin - 3); + } + mpeg_bits_seek(reader, offset); // restore + } + + mpeg_bits_seek(reader, origin); + return -1; +} +#endif + +static int ps_sync_start_probe(struct mpeg_bits_t* reader) +{ + if (0x000001 == mpeg_bits_tryread(reader, 3)) + return MPEG_ERROR_OK; + + if (mpeg_bits_tell(reader) + 3 > mpeg_bits_length(reader)) + return MPEG_ERROR_NEED_MORE_DATA; + + return MPEG_ERROR_INVALID_DATA; +} + +static int ps_demuxer_onpes(void* param, int program, int stream, int codecid, int flags, int64_t pts, int64_t dts, const void* data, size_t bytes) +{ + struct ps_demuxer_t* ps; + ps = (struct ps_demuxer_t*)param; + assert(0 == program); // unused(ts demux only) + return ps->onpacket(ps->param, stream, codecid, flags, pts, dts, data, bytes); +} + +static struct pes_t* psm_fetch(struct psm_t* psm, uint8_t sid) +{ + size_t i; + for (i = 0; i < psm->stream_count; ++i) + { + if (psm->streams[i].sid == sid) + return &psm->streams[i]; + } + + if (psm->stream_count < sizeof(psm->streams) / sizeof(psm->streams[0])) + { + // '110x xxxx' + // ISO/IEC 13818-3 or ISO/IEC 11172-3 or ISO/IEC 13818-7 or + // ISO/IEC 14496-3 or ISO/IEC 23008-3 audio stream number 'x xxxx' + + // '1110 xxxx' + // Rec. ITU-T H.262 | ISO/IEC 13818-2, ISO/IEC 11172-2, ISO/IEC 14496-2, + // Rec. ITU-T H.264 | ISO/IEC 14496-10 or + // Rec. ITU-T H.265 | ISO/IEC 23008-2 video stream number 'xxxx' + + // guess stream codec id +#if defined(MPEG_GUESS_STREAM) || defined(MPEG_H26X_VERIFY) + if (0xE0 <= sid && sid <= 0xEF) + { + psm->streams[psm->stream_count].pid = sid; + psm->streams[psm->stream_count].codecid = PSI_STREAM_RESERVED; // unknown + return &psm->streams[psm->stream_count++]; + } +#endif +#if defined(MPEG_GUESS_STREAM) + if(0xC0 <= sid && sid <= 0xDF) + { + psm->streams[psm->stream_count].pid = sid; + psm->streams[psm->stream_count].codecid = PSI_STREAM_AAC; + return &psm->streams[psm->stream_count++]; + } +#endif + } + + return NULL; +} + +static int ps_demuxer_packet(struct ps_demuxer_t *ps, const uint8_t* data, size_t bytes, size_t *consume) +{ + int r; + struct pes_t* pes; + + pes = ps->pes; + +#if defined(MPEG_LIVING_VIDEO_FRAME_DEMUX) + // video packet size > 0xFFFF, split by pts/dts + if (PES_SID_VIDEO == pes->sid + && PSI_STREAM_H264 != pes->codecid && PSI_STREAM_H265 != pes->codecid && PSI_STREAM_H266 != pes->codecid + && (pes->pkt.size > 0 || pes->len + pes->PES_header_data_length + 3 == 0xFFFF)) + pes->len = 0; +#endif + + pes->flags = pes->data_alignment_indicator ? MPEG_FLAG_IDR_FRAME : 0; + r = pes_packet(&pes->pkt, pes, data, bytes, consume, ps->start, ps_demuxer_onpes, ps); + ps->start = 0; // clear start flags + return r; +} + +static int ps_demuxer_skip(struct ps_demuxer_t* ps, struct mpeg_bits_t* reader, uint8_t v8) +{ + // '110x xxxx' ISO/IEC 13818-3 or ISO/IEC 11172-3 or ISO/IEC 13818-7 or ISO/IEC 14496-3 or ISO/IEC 23008-3 audio stream number 'x xxxx' + // '1110 xxxx' Rec. ITU-T H.262 | ISO/IEC 13818-2, ISO/IEC 11172-2, ISO/IEC 14496-2, Rec. ITU-T H.264 | ISO/IEC 14496-10, Rec. ITU-T H.265 | ISO/IEC 23008-2, Rec. ITU-T H.266 | ISO/IEC 23090-3 or ISO/IEC 23094-1 video stream number 'xxxx' + // '1111 1101' extended_stream_id + if ((v8 >= 0xC0 && v8 <= 0xDF) || (v8 >= 0xE0 && v8 <= 0xEF) || v8 == PES_SID_PRIVATE_1 || v8 == 0xFD) + { + // skip PES + mpeg_bits_skip(reader, mpeg_bits_read16(reader)); + if (mpeg_bits_error(reader)) + return MPEG_ERROR_NEED_MORE_DATA; + } + else + { + // backward 1-step + mpeg_bits_seek(reader, mpeg_bits_tell(reader) - 1); + assert(0 == mpeg_bits_error(reader)); + ps->sync = 0; // wait for next start packet + } + + return MPEG_ERROR_OK; +} + +static int ps_demuxer_header(struct ps_demuxer_t* ps, struct mpeg_bits_t* reader) +{ + int r; + size_t n; + size_t off; + uint8_t v8; + struct pes_t* pes; + + for (off = mpeg_bits_tell(reader); 0 == ps_demuxer_find_startcode(reader); off = mpeg_bits_tell(reader)) + { + r = MPEG_ERROR_OK; + v8 = mpeg_bits_read8(reader); + if (mpeg_bits_error(reader)) + break; + + if (!ps->sync && v8 != PES_SID_START) + { + // backward 1-step + mpeg_bits_seek(reader, mpeg_bits_tell(reader) - 1); + assert(0 == mpeg_bits_error(reader)); + continue; // wait for 00 00 01 BA + } + + // fix HIK H.265: 00 00 01 BA 00 00 01 E0 ... + if (0x000001 == (mpeg_bits_tryread(reader, 3) & 0xFFFFFF)) + continue; + + switch (v8) + { + case PES_SID_START: + r = pack_header_read(&ps->pkhd, reader); + r = MPEG_ERROR_OK == r ? ps_sync_start_probe(reader) : r; + ps->start = MPEG_ERROR_OK == r ? 1 : ps->start; + ps->sync = MPEG_ERROR_OK == r ? 1 : 0; + break; + + case PES_SID_SYS: + r = system_header_read(&ps->system, reader); + break; + + case PES_SID_END: + r = MPEG_ERROR_OK; + break; + + case PES_SID_PSM: + n = ps->psm.stream_count; + r = psm_read(&ps->psm, reader); + if (n != ps->psm.stream_count || ps->ver != ps->psm.ver) + ps_demuxer_notify(ps); // TODO: check psm stream sid + ps->ver = ps->psm.ver; + break; + + case PES_SID_PSD: + r = psd_read(&ps->psd, reader); + break; + + case PES_SID_PRIVATE_2: + case PES_SID_ECM: + case PES_SID_EMM: + case PES_SID_DSMCC: + case PES_SID_H222_E: + // stream data + case PES_SID_PADDING: + // padding + mpeg_bits_skip(reader, mpeg_bits_read16(reader)); + r = mpeg_bits_error(reader) ? MPEG_ERROR_NEED_MORE_DATA : MPEG_ERROR_OK; + break; + + // ffmpeg mpeg.c mpegps_read_pes_header + //case 0x1c0: + //case 0x1df: + //case 0x1e0: + //case 0x1ef: + //case 0x1bd: + //case 0x01fd: + // break; + + default: + pes = psm_fetch(&ps->psm, v8); + if (pes) + { + pes->sid = v8; + r = ps->pkhd.mpeg2 ? pes_read_header(pes, reader) : pes_read_mpeg1_header(pes, reader); + if (MPEG_ERROR_OK == r) + { +#if defined(MPEG_ZERO_PAYLOAD_LENGTH) + // fix H.264/H.265 ps payload zero-length + if (0 == pes->len && 0xE0 <= pes->sid && pes->sid <= 0xEF) + { + r = ps_demuxer_find_pes_start(reader); + if (-1 == r) + return (int)off; + + pes->len = r; + r = MPEG_ERROR_OK; + } +#endif + + ps->pes = pes; + ps->pes_length = 0; // init + ps->state = PS_DEMUXER_STATE_DATA; // next state: handle packet + return (int)mpeg_bits_tell(reader); + } + } + else // pes == NULL + { + r = ps_demuxer_skip(ps, reader, v8); + } + break; + } + + if (r < 0) + return r; // user handler error + else if (r == MPEG_ERROR_NEED_MORE_DATA) + break; + } + + return (int)off; +} + +static int ps_demuxer_buffer(struct ps_demuxer_t* ps, const uint8_t* data, size_t bytes) +{ + void* ptr; + if (ps->buffer.len + bytes > ps->buffer.cap) + { + if (ps->buffer.len + bytes > N_BUFFER_MAX) + return -E2BIG; + + ptr = realloc(ps->buffer.ptr == (uint8_t*)(ps + 1) ? NULL : ps->buffer.ptr, ps->buffer.len + bytes + N_BUFFER_INC); + if (!ptr) + return -ENOMEM; + + if (ps->buffer.ptr == (uint8_t*)(ps + 1)) + memcpy(ptr, ps->buffer.ptr, ps->buffer.len); + ps->buffer.ptr = (uint8_t*)ptr; + ps->buffer.cap = ps->buffer.len + bytes + N_BUFFER_INC; + } + + memmove(ps->buffer.ptr + ps->buffer.len, data, bytes); + ps->buffer.len += bytes; + return 0; +} + +int ps_demuxer_input(struct ps_demuxer_t* ps, const uint8_t* data, size_t bytes) +{ + int r; + size_t i, consume; + struct mpeg_bits_t reader; + + for(i = 0; i < bytes; ) + { + switch (ps->state) + { + case PS_DEMUXER_STATE_START: + mpeg_bits_init2(&reader, ps->buffer.ptr, ps->buffer.len, data + i, bytes - i); + + r = ps_demuxer_header(ps, &reader); + if (r >= 0) + { + assert(r <= ps->buffer.len + (bytes - i)); + if (r >= ps->buffer.len) + { + r -= (int)ps->buffer.len; + i += r; + ps->buffer.len = 0; + } + else if(r > 0) + { + memmove(ps->buffer.ptr, ps->buffer.ptr + r, ps->buffer.len - r); + ps->buffer.len -= r; + } + else + { + assert(0 == r); + } + + if (PS_DEMUXER_STATE_START == ps->state && i < bytes) + { + // stash buffer + r = ps_demuxer_buffer(ps, data + i, bytes - i); + return 0 == r ? (int)bytes : r; + } + } + break; + + case PS_DEMUXER_STATE_DATA: + assert(ps->pes && ps->pes_length <= ps->pes->len); + + // cache + if (ps->buffer.len > 0) + { + if (ps->buffer.len >= ps->pes->len - ps->pes_length) + { + r = ps_demuxer_packet(ps, ps->buffer.ptr, ps->pes->len - ps->pes_length, &consume); + memmove(ps->buffer.ptr, ps->buffer.ptr + consume, ps->buffer.len - consume); + ps->buffer.len -= consume; // ps->pes->len - ps->pes_length; // fix: pack start code in video data + ps->pes_length = 0; + ps->start = 0; // clear start flag + ps->state = PS_DEMUXER_STATE_START; // next round + + //next round + } + else + { + // need more data + r = ps_demuxer_packet(ps, ps->buffer.ptr, ps->buffer.len, &consume); + ps->pes_length = consume == ps->buffer.len ? ps->pes_length + consume : 0; + ps->start = consume == ps->buffer.len ? ps->start : 0; + ps->state = consume == ps->buffer.len ? ps->state : PS_DEMUXER_STATE_START; + ps->buffer.len = 0; // clear + + // fall through + } + } + + if (PS_DEMUXER_STATE_DATA == ps->state) + { + if (bytes - i >= ps->pes->len - ps->pes_length) + { + r = ps_demuxer_packet(ps, data + i, ps->pes->len - ps->pes_length, &consume); + i += consume; // ps->pes->len - ps->pes_length; // fix: pack start code in video data + ps->pes_length = 0; + ps->start = 0; // clear start flag + ps->state = PS_DEMUXER_STATE_START; // next round + } + else + { + // need more data + r = ps_demuxer_packet(ps, data + i, bytes - i, &consume); + ps->pes_length = consume == bytes - i ? ps->pes_length + (bytes - i) : 0; + ps->start = consume == bytes - i ? ps->start : 0; + ps->state = consume == bytes - i ? ps->state : PS_DEMUXER_STATE_START; + i += consume; //i = bytes; // fix: pack start code in video data + } + } + + break; + + default: + assert(0); + return -1; + } + + if (r < 0) + { + ps->state = PS_DEMUXER_STATE_START; // skip error, try find next start code + ps->buffer.len = 0; // clear buffer + return r; + } + } + + return (int)bytes; +} + +struct ps_demuxer_t* ps_demuxer_create(ps_demuxer_onpacket onpacket, void* param) +{ + struct ps_demuxer_t* ps; + ps = calloc(1, sizeof(struct ps_demuxer_t) + N_BUFFER_INIT); + if(!ps) + return NULL; + + ps->state = PS_DEMUXER_STATE_START; + ps->pkhd.mpeg2 = 1; + ps->onpacket = onpacket; + ps->param = param; + + ps->buffer.ptr = (uint8_t*)(ps + 1); + ps->buffer.cap = N_BUFFER_INIT; + + ps->ver = 0xFFFFFFFF; // fix: guest stream add stream internal, alway notify on firstly + return ps; +} + +int ps_demuxer_destroy(struct ps_demuxer_t* ps) +{ + size_t i; + struct pes_t* pes; + for (i = 0; i < ps->psm.stream_count; i++) + { + pes = &ps->psm.streams[i]; + if (pes->pkt.data) + free(pes->pkt.data); + pes->pkt.data = NULL; + } + + if (ps->buffer.ptr != (uint8_t*)(ps + 1)) + { + assert(ps->buffer.cap > N_BUFFER_INIT); + free(ps->buffer.ptr); + ps->buffer.ptr = NULL; + } + free(ps); + return 0; +} + +void ps_demuxer_set_notify(struct ps_demuxer_t* ps, struct ps_demuxer_notify_t *notify, void* param) +{ + ps->notify_param = param; + memcpy(&ps->notify, notify, sizeof(ps->notify)); +} + +static void ps_demuxer_notify(struct ps_demuxer_t* ps) +{ + size_t i; + struct pes_t* pes; + if (!ps->notify.onstream) + return; + + for (i = 0; i < ps->psm.stream_count; i++) + { + pes = &ps->psm.streams[i]; + ps->notify.onstream(ps->notify_param, pes->pid, pes->codecid, pes->esinfo, pes->esinfo_len, i + 1 >= ps->psm.stream_count ? 1 : 0); + } +} diff --git a/MediaServer/libmpeg/source/mpeg-ps-enc.c b/MediaServer/libmpeg/source/mpeg-ps-enc.c new file mode 100644 index 0000000..ec70966 --- /dev/null +++ b/MediaServer/libmpeg/source/mpeg-ps-enc.c @@ -0,0 +1,295 @@ +// ITU-T H.222.0(06/2012) +// Information technology - Generic coding of moving pictures and associated audio information: Systems +// 2.5.3.1 Program stream(p74) + +#include "mpeg-pes-internal.h" +#include "mpeg-ps-internal.h" +#include "mpeg-util.h" +#include "mpeg-ps.h" +#include +#include +#include +#include +#include +#include + +#define MAX_PES_HEADER 1024 // pack_header + system_header + psm +#define MAX_PES_PACKET 0xFF80 // 64k pes data, reserved 0x7F for hik + +struct ps_muxer_t +{ + struct psm_t psm; + struct ps_pack_header_t pack; + struct ps_system_header_t system; + + int h26x_with_aud; + unsigned int psm_period; + unsigned int scr_period; + + struct ps_muxer_func_t func; + void* param; + +// uint8_t packet[MAX_PACKET_SIZE]; +}; + +static struct pes_t* ps_stream_find(struct ps_muxer_t *ps, int streamid) +{ + size_t i; + for (i = 0; i < ps->psm.stream_count; i++) + { + if (streamid == ps->psm.streams[i].sid) + return &ps->psm.streams[i]; + } + return NULL; +} + +int ps_muxer_input(struct ps_muxer_t* ps, int streamid, int flags, int64_t pts, int64_t dts, const void* data, size_t bytes) +{ + int r, first; + size_t i, n, sz; + uint8_t *packet; + struct pes_t* stream; + const uint8_t* payload; + + i = 0; + first = 1; + payload = (const uint8_t*)data; + + stream = ps_stream_find(ps, streamid); + if (NULL == stream) return -1; // not found + stream->data_alignment_indicator = (flags & MPEG_FLAG_IDR_FRAME) ? 1 : 0; // idr frame + stream->pts = pts; + stream->dts = dts; + + // Add PSM for IDR frame + ps->psm_period = ((flags & MPEG_FLAG_IDR_FRAME) && mpeg_stream_type_video(stream->codecid)) ? 0 : ps->psm_period; + ps->h26x_with_aud = (flags & MPEG_FLAG_H264_H265_WITH_AUD) ? 1 : 0; + + // TODO: + // 1. update packet header program_mux_rate + // 2. update system header rate_bound + + // alloc once (include Multi-PES packet) + sz = bytes + MAX_PES_HEADER + (bytes/MAX_PES_PACKET+1) * 64; // 64 = 0x000001 + stream_id + PES_packet_length + other + packet = ps->func.alloc(ps->param, sz); + if(!packet) return -ENOMEM; + + // write pack_header(p74) + // 2.7.1 Frequency of coding the system clock reference + // http://www.bretl.com/mpeghtml/SCR.HTM + //the maximum allowed interval between SCRs is 700ms + //ps->pack.system_clock_reference_base = (dts-3600) % (((int64_t)1)<<33); + ps->pack.system_clock_reference_base = dts >= 3600 ? (dts - 3600) : 0; + ps->pack.system_clock_reference_extension = 0; + ps->pack.program_mux_rate = 6106; + i += pack_header_write(&ps->pack, packet + i); + +#if !defined(MPEG_FIX_VLC_3_X_PS_SYSTEM_HEADER) + // https://github.com/videolan/vlc/blob/3.0.x/modules/demux/mpeg/ps.h#L488 + // fix ps_pkt_parse_system -> ps_track_fill with default mp1/2 audio codec(without psm) + + // write system_header(p76) + if(0 == (ps->psm_period % 30)) + i += system_header_write(&ps->system, packet + i); +#endif + + // write program_stream_map(p79) + if (0 == (ps->psm_period % 30)) + { +#if defined(MPEG_CLOCK_EXTENSION_DESCRIPTOR) + ps->psm.clock = time() * 1000; // todo: gettimeofday +#endif + i += psm_write(&ps->psm, packet + i); + } + + // check packet size + assert(i < MAX_PES_HEADER); + + // write data + while(bytes > 0) + { + uint8_t *p; + uint8_t *pes = packet + i; + + p = pes + pes_write_header(stream, pes, sz - i); + stream->pts = stream->dts = PTS_NO_VALUE; // clear pts/dts flags + stream->data_alignment_indicator = 0; // clear flags + assert(p - pes < 64); + + if(first) + { + if (PSI_STREAM_H264 == stream->codecid && !ps->h26x_with_aud) + { + // 2.14 Carriage of Rec. ITU-T H.264 | ISO/IEC 14496-10 video + // Each AVC access unit shall contain an access unit delimiter NAL Unit + nbo_w32(p, 0x00000001); + p[4] = 0x09; // AUD + p[5] = 0xE0; // any slice type (0xe) + rbsp stop one bit + p += 6; + } + else if (PSI_STREAM_H265 == stream->codecid && !ps->h26x_with_aud) + { + // 2.17 Carriage of HEVC + // Each HEVC access unit shall contain an access unit delimiter NAL unit. + nbo_w32(p, 0x00000001); + p[4] = 0x46; // 35-AUD_NUT + p[5] = 0x01; + p[6] = 0x50; // B&P&I (0x2) + rbsp stop one bit + p += 7; + } + else if (PSI_STREAM_H266 == stream->codecid && !ps->h26x_with_aud) + { + // 2.23 Carriage of VVC + // Each VVC access unit shall contain an access unit delimiter NAL unit + nbo_w32(p, 0x00000001); + p[4] = 0x00; // 20-AUD_NUT + p[5] = 0xA1; + p[6] = 0x18; // B&P&I (0x1) + rbsp stop one bit + p += 7; + } + } + + // PES_packet_length = PES-Header + Payload-Size + // A value of 0 indicates that the PES packet length is neither specified nor bounded + // and is allowed only in PES packets whose payload consists of bytes from a + // video elementary stream contained in transport stream packets + if((p - pes - 6) + bytes > MAX_PES_PACKET) + { + nbo_w16(pes + 4, MAX_PES_PACKET); + n = MAX_PES_PACKET - (p - pes - 6); + } + else + { + nbo_w16(pes + 4, (uint16_t)((p - pes - 6) + bytes)); + n = bytes; + } + + memcpy(p, payload, n); + payload += n; + bytes -= n; + + // notify packet already + i += n + (p - pes); + +// i = 0; // clear value, the next pes packet don't need pack_header + first = 0; // clear first packet flag + pts = dts = 0; // only first packet write PTS/DTS + } + + assert(i < sz); + r = ps->func.write(ps->param, stream->sid, packet, i); + ps->func.free(ps->param, packet); + + ++ps->psm_period; + return r; +} + +struct ps_muxer_t* ps_muxer_create(const struct ps_muxer_func_t *func, void* param) +{ + struct ps_muxer_t *ps = NULL; + + assert(func); + ps = (struct ps_muxer_t *)calloc(1, sizeof(struct ps_muxer_t)); + if(!ps) + return NULL; + + memcpy(&ps->func, func, sizeof(ps->func)); + ps->param = param; + + ps->system.rate_bound = 26234; //10493600~10mbps(50BPS * 8 = 400bps) +// ps->system.audio_bound = 1; // [0,32] max active audio streams +// ps->system.video_bound = 1; // [0,16] max active video streams + ps->system.fixed_flag = 0; // 1-fixed bitrate, 0-variable bitrate + ps->system.CSPS_flag = 0; // meets the constraints defined in 2.7.9. + ps->system.packet_rate_restriction_flag = 0; // dependence CSPS_flag + ps->system.system_audio_lock_flag = 0; // all audio stream sampling rate is constant + ps->system.system_video_lock_flag = 0; // all video stream frequency is constant + + //ps->psm.ver = 1; + //ps->psm.stream_count = 2; + //ps->psm.streams[0].element_stream_id = PES_SID_VIDEO; + //ps->psm.streams[0].stream_type = PSI_STREAM_H264; + //ps->psm.streams[1].element_stream_id = PES_SID_AUDIO; + //ps->psm.streams[1].stream_type = PSI_STREAM_AAC; + + return ps; +} + +int ps_muxer_destroy(struct ps_muxer_t* ps) +{ + size_t i; + for (i = 0; i < ps->psm.stream_count; i++) + { + if (ps->psm.streams[i].esinfo) + { + free(ps->psm.streams[i].esinfo); + ps->psm.streams[i].esinfo = NULL; + } + } + + free(ps); + return 0; +} + +int ps_muxer_add_stream(struct ps_muxer_t* ps, int codecid, const void* extradata, size_t bytes) +{ + struct psm_t* psm; + struct pes_t* pes; + + assert(bytes < 512); + if (!ps || ps->psm.stream_count >= sizeof(ps->psm.streams) / sizeof(ps->psm.streams[0])) + { + assert(0); + return -1; + } + + psm = &ps->psm; + pes = &psm->streams[psm->stream_count]; + + if (mpeg_stream_type_video(codecid)) + { + pes->sid = (uint8_t)(PES_SID_VIDEO + ps->system.video_bound); + + assert(ps->system.video_bound + 1 < 16); + ++ps->system.video_bound; // [0,16] max active video streams + ps->system.streams[ps->system.stream_count].buffer_bound_scale = 1; + /* FIXME -- VCD uses 46, SVCD uses 230, ffmpeg has 230 with a note that it is small */ + ps->system.streams[ps->system.stream_count].buffer_size_bound = 400 /* 8191-13 bits max value */; + } + else if (mpeg_stream_type_audio(codecid)) + { + pes->sid = (uint8_t)(PES_SID_AUDIO + ps->system.audio_bound); + + assert(ps->system.audio_bound + 1 < 32); + ++ps->system.audio_bound; // [0,32] max active audio streams + ps->system.streams[ps->system.stream_count].buffer_bound_scale = 0; + /* This value HAS to be used for VCD (see VCD standard, p. IV-7). + * Right now it is also used for everything else. */ + ps->system.streams[ps->system.stream_count].buffer_size_bound = 32 /* 4 * 1024 / 128 */; + } + else + { + assert(0); + return -1; + } + + if (bytes > 0) + { + pes->esinfo = (uint8_t*)malloc(bytes); + if (!pes->esinfo) + return -1; + memcpy(pes->esinfo, extradata, bytes); + pes->esinfo_len = (uint16_t)bytes; + } + + assert(psm->stream_count == ps->system.stream_count); + ps->system.streams[ps->system.stream_count].stream_id = pes->sid; + ++ps->system.stream_count; + + pes->codecid = (uint8_t)codecid; + ++psm->stream_count; + ++psm->ver; + + ps->psm_period = 0; // immediate update psm + return pes->sid; +} diff --git a/MediaServer/libmpeg/source/mpeg-ps-internal.h b/MediaServer/libmpeg/source/mpeg-ps-internal.h new file mode 100644 index 0000000..9680be7 --- /dev/null +++ b/MediaServer/libmpeg/source/mpeg-ps-internal.h @@ -0,0 +1,87 @@ +#ifndef _mpeg_ps_internal_h_ +#define _mpeg_ps_internal_h_ + +#include "mpeg-proto.h" +#include "mpeg-types.h" +#include "mpeg-pes-internal.h" +#include "mpeg-util.h" + +//#define NSTREAM 48 // 32-audio('110xxxxx') + 16-video('1110xxxx') +#define N_ACCESS_UNIT 16 + +#define SEQUENCE_END_CODE (0x000001B7) + +struct ps_pack_header_t +{ + int mpeg2; // 1-mpeg2, other-mpeg1 + + int64_t system_clock_reference_base; + uint32_t system_clock_reference_extension; + + uint32_t program_mux_rate; +}; + +struct ps_stream_header_t +{ + uint32_t stream_id : 8; + uint32_t stream_extid : 8; + uint32_t buffer_bound_scale : 1; + uint32_t buffer_size_bound : 13; +}; + +struct ps_system_header_t +{ + uint32_t rate_bound; + uint32_t audio_bound : 6; + uint32_t fixed_flag : 1; + uint32_t CSPS_flag : 1; + uint32_t system_audio_lock_flag : 1; + uint32_t system_video_lock_flag : 1; + uint32_t video_bound : 5; + uint32_t packet_rate_restriction_flag : 1; + + uint32_t stream_count; + struct ps_stream_header_t streams[16]; +}; + +struct psm_t +{ + uint32_t ver : 5; // version_number : 5; + + struct pes_t streams[16]; + size_t stream_count; + + int64_t clock; // ms +}; + +struct psd_t +{ + uint64_t prev_directory_offset; + uint64_t next_directory_offset; + + struct access_unit_t + { + uint8_t packet_stream_id; + uint8_t pes_header_position_offset_sign; + uint64_t PTS; + uint64_t pes_header_position_offset; + uint16_t reference_offset; + uint32_t bytes_to_read; + uint8_t packet_stream_id_extension_msbs; + uint8_t packet_stream_id_extension_lsbs; + uint8_t intra_coded_indicator; + uint8_t coding_parameters_indicator; + } units[N_ACCESS_UNIT]; +}; + +int psm_read(struct psm_t *psm, struct mpeg_bits_t* reader); +size_t psm_write(const struct psm_t *psm, uint8_t *data); + +int psd_read(struct psd_t *psd, struct mpeg_bits_t* reader); + +int pack_header_read(struct ps_pack_header_t *h, struct mpeg_bits_t* reader); +int system_header_read(struct ps_system_header_t *h, struct mpeg_bits_t* reader); +size_t pack_header_write(const struct ps_pack_header_t *h, uint8_t *data); +size_t system_header_write(const struct ps_system_header_t *h, uint8_t *data); + +#endif /* !_mpeg_ps_internal_h_ */ diff --git a/MediaServer/libmpeg/source/mpeg-psd.c b/MediaServer/libmpeg/source/mpeg-psd.c new file mode 100644 index 0000000..e5fe16e --- /dev/null +++ b/MediaServer/libmpeg/source/mpeg-psd.c @@ -0,0 +1,158 @@ +// ITU-T H.222.0(10/2014) +// Information technology - Generic coding of moving pictures and associated audio information: Systems +// 2.5.5 Program stream directory(p84) + +#include "mpeg-ps-internal.h" +#include + +#if 1 +int psd_read(struct psd_t* psd, struct mpeg_bits_t* reader) +{ + size_t i, end; + size_t packet_length; + uint8_t v8; + uint64_t v64; + uint16_t number_of_access_units; + + // Table 2-42 - Program stream directory packet(p81) + packet_length = mpeg_bits_read16(reader); + end = mpeg_bits_tell(reader) + packet_length; + if (mpeg_bits_error(reader) || end > mpeg_bits_length(reader)) + return MPEG_ERROR_NEED_MORE_DATA; + + //assert((0x01 & data[7]) == 0x01); // 'xxxxxxx1' + number_of_access_units = mpeg_bits_read15(reader); // (data[6] << 7) | ((data[7] >> 1) & 0x7F); + assert(number_of_access_units <= N_ACCESS_UNIT); + + //assert((0x01 & data[9]) == 0x01); // 'xxxxxxx1' + //assert((0x01 & data[11]) == 0x01); // 'xxxxxxx1' + //assert((0x01 & data[13]) == 0x01); // 'xxxxxxx1' + psd->prev_directory_offset = mpeg_bits_read45(reader); + //assert((0x01 & data[15]) == 0x01); // 'xxxxxxx1' + //assert((0x01 & data[17]) == 0x01); // 'xxxxxxx1' + //assert((0x01 & data[19]) == 0x01); // 'xxxxxxx1' + psd->next_directory_offset = mpeg_bits_read45(reader); + + // access unit + for (i = 0; 0 == mpeg_bits_error(reader) && mpeg_bits_tell(reader) + 18 <= end && i < number_of_access_units && i < N_ACCESS_UNIT; i++) + { + psd->units[i].packet_stream_id = mpeg_bits_read8(reader); + //assert((0x01 & data[2]) == 0x01); // 'xxxxxxx1' + //assert((0x01 & data[4]) == 0x01); // 'xxxxxxx1' + //assert((0x01 & data[6]) == 0x01); // 'xxxxxxx1' + v64 = mpeg_bits_read45(reader); + psd->units[i].pes_header_position_offset = v64 & 0x3FFFFF; + psd->units[i].pes_header_position_offset_sign = (psd->units[i].pes_header_position_offset & 0x400000) ? 1 : 0; + psd->units[i].reference_offset = mpeg_bits_read16(reader); + + //assert((0x81 & data[9]) == 0x81); // '1xxxxxx1' + v8 = mpeg_bits_read8(reader); + if (psd->units[i].packet_stream_id == 0xFD) + { + psd->units[i].packet_stream_id_extension_msbs = (v8 >> 4) & 0x07; + } + else + { + assert((0x70 & v8) == 0x00); // '1000xxx1' + } + + //assert((0x01 & data[11]) == 0x01); // 'xxxxxxx1' + //assert((0x01 & data[13]) == 0x01); // 'xxxxxxx1' + psd->units[i].PTS = (((v8 >> 1) & 0x07) << 30) | mpeg_bits_read30(reader); + + //assert((0x01 & data[15]) == 0x01); // 'xxxxxxx1' + psd->units[i].bytes_to_read = mpeg_bits_read15(reader) << 15; + psd->units[i].bytes_to_read |= mpeg_bits_read8(reader); + + v8 = mpeg_bits_read8(reader); + //assert((0x80 & data[17]) == 0x80); // '1xxxxxxx' + psd->units[i].intra_coded_indicator = (v8 >> 6) & 0x01; + psd->units[i].coding_parameters_indicator = (v8 >> 4) & 0x03; + if (0xFD == psd->units[i].packet_stream_id) + { + psd->units[i].packet_stream_id_extension_lsbs = v8 & 0x0F; + } + else + { + assert((0x0F & v8) == 0x00); // '1xxx0000' + } + } + + assert(0 == mpeg_bits_error(reader)); + assert(end == mpeg_bits_tell(reader)); + return MPEG_ERROR_OK; +} +#else +size_t psd_read(struct psd_t *psd, const uint8_t* data, size_t bytes) +{ + size_t i, j; + size_t packet_length; + uint16_t number_of_access_units; + + // Table 2-42 - Program stream directory packet(p81) + assert(0x00==data[0] && 0x00==data[1] && 0x01==data[2] && 0xFF==data[3]); + packet_length = (((uint16_t)data[4]) << 8) | data[5]; + assert(bytes >= (size_t)packet_length + 6); + if (bytes < 20 || packet_length < 20 - 6) + return 0; // invalid data length + + assert((0x01 & data[7]) == 0x01); // 'xxxxxxx1' + number_of_access_units = (data[6] << 7) | ((data[7] >> 1) & 0x7F); + assert(number_of_access_units <= N_ACCESS_UNIT); + + assert((0x01 & data[9]) == 0x01); // 'xxxxxxx1' + assert((0x01 & data[11]) == 0x01); // 'xxxxxxx1' + assert((0x01 & data[13]) == 0x01); // 'xxxxxxx1' + psd->prev_directory_offset = (uint64_t)(((uint64_t)data[8] << 38) | ((((uint64_t)data[9] >> 1) & 0x7F) << 30) | ((uint64_t)data[10] << 22) | ((((uint64_t)data[11] >> 1) & 0x7F) << 15) | ((uint64_t)data[12] << 7) | (((uint64_t)data[13] >> 1) & 0x7F)); + assert((0x01 & data[15]) == 0x01); // 'xxxxxxx1' + assert((0x01 & data[17]) == 0x01); // 'xxxxxxx1' + assert((0x01 & data[19]) == 0x01); // 'xxxxxxx1' + psd->next_directory_offset = (uint64_t)(((uint64_t)data[14] << 38) | ((((uint64_t)data[15] >> 1) & 0x7F) << 30) | ((uint64_t)data[16] << 22) | ((((uint64_t)data[17] >> 1) & 0x7F) << 15) | ((uint64_t)data[18] << 7) | (((uint64_t)data[19] >> 1) & 0x7F)); + + // access unit + j = 20; + for(i = 0; j + 18 <= packet_length + 6 && i < number_of_access_units && i < N_ACCESS_UNIT; i++) + { + psd->units[i].packet_stream_id = data[j]; + psd->units[i].pes_header_position_offset_sign = (data[j+1] >> 7) & 0x01; + assert((0x01 & data[j+2]) == 0x01); // 'xxxxxxx1' + assert((0x01 & data[j+4]) == 0x01); // 'xxxxxxx1' + assert((0x01 & data[j+6]) == 0x01); // 'xxxxxxx1' + psd->units[i].pes_header_position_offset = (uint64_t)(((uint64_t)(data[j+1] & 0x7F) << 37) | ((((uint64_t)data[j+2] >> 1) & 0x7F) << 30) | ((uint64_t)data[j+3] << 22) | ((((uint64_t)data[j+4] >> 1) & 0x7F) << 15) | ((uint64_t)data[j+5] << 7) | (((uint64_t)data[j+6] >> 1) & 0x7F)); + psd->units[i].reference_offset = (data[j+7] << 8) | data[j+8]; + + assert((0x81 & data[j+9]) == 0x81); // '1xxxxxx1' + if(psd->units[i].packet_stream_id == 0xFD) + { + psd->units[i].packet_stream_id_extension_msbs = (data[j+9] >> 4) & 0x07; + } + else + { + assert((0x70 & data[j+9]) == 0x00); // '1000xxx1' + } + + assert((0x01 & data[j+11]) == 0x01); // 'xxxxxxx1' + assert((0x01 & data[j+13]) == 0x01); // 'xxxxxxx1' + psd->units[i].PTS = (uint64_t)(((uint64_t)((data[j+9] >> 1) & 0x07) << 30) | ((uint64_t)data[j+10] << 22) | ((((uint64_t)data[j+11] >> 1) & 0x7F) << 15) | ((uint64_t)data[j+12] << 7) | (((uint64_t)data[j+13] >> 1) & 0x7F)); + + assert((0x01 & data[j+15]) == 0x01); // 'xxxxxxx1' + psd->units[i].bytes_to_read = (uint32_t)( ((uint32_t)data[j+14] << 15) | (((data[j+15] >> 1) & 0x7F) << 8) | data[j+16]); + + assert((0x80 & data[j+17]) == 0x80); // '1xxxxxxx' + psd->units[i].intra_coded_indicator = (data[j+17] >> 6) & 0x01; + psd->units[i].coding_parameters_indicator = (data[j+17] >> 4) & 0x03; + if(0xFD == psd->units[i].packet_stream_id) + { + psd->units[i].packet_stream_id_extension_lsbs = data[j+17] & 0x0F; + } + else + { + assert((0x0F & data[j+17]) == 0x00); // '1xxx0000' + } + + j += 18; + } + + return j; +} +#endif \ No newline at end of file diff --git a/MediaServer/libmpeg/source/mpeg-psm.c b/MediaServer/libmpeg/source/mpeg-psm.c new file mode 100644 index 0000000..a6a106e --- /dev/null +++ b/MediaServer/libmpeg/source/mpeg-psm.c @@ -0,0 +1,273 @@ +// ITU-T H.222.0(10/2014) +// Information technology - Generic coding of moving pictures and associated audio information: Systems +// 2.5.4 Program stream map(p82) + +#include "mpeg-ps-internal.h" +#include "mpeg-pes-internal.h" +#include "mpeg-element-descriptor.h" +#include "mpeg-util.h" +#include +#include + +static struct pes_t* psm_fetch(struct psm_t* psm, uint8_t sid) +{ + size_t i; + for (i = 0; i < psm->stream_count; i++) + { + if (psm->streams[i].sid == sid) + return &psm->streams[i]; + } + + if (psm->stream_count >= sizeof(psm->streams) / sizeof(psm->streams[0])) + { + assert(0); + return NULL; + } + + // new stream + return &psm->streams[psm->stream_count++]; +} + +#if 1 +int psm_read(struct psm_t* psm, struct mpeg_bits_t* reader) +{ + uint8_t v8; + size_t end, off; + struct pes_t* stream; + //uint8_t current_next_indicator; + uint8_t single_extension_stream_flag; + uint16_t program_stream_map_length; + uint16_t program_stream_info_length; + uint16_t element_stream_map_length; + uint16_t element_stream_info_length; + uint8_t cid, sid; + + // Table 2-41 - Program stream map(p79) + program_stream_map_length = mpeg_bits_read16(reader); // (data[4] << 8) | data[5]; + end = mpeg_bits_tell(reader) + program_stream_map_length; + if (mpeg_bits_error(reader) || end > mpeg_bits_length(reader)) + return MPEG_ERROR_NEED_MORE_DATA; + + v8 = mpeg_bits_read8(reader); // data[6] + //assert((0x20 & data[6]) == 0x00); // 'xx0xxxxx' + //current_next_indicator = (data[6] >> 7) & 0x01; + single_extension_stream_flag = (uint8_t)(v8 >> 6) & 0x01; //(data[6] >> 6) & 0x01; + psm->ver = v8 & 0x1F; + mpeg_bits_read8(reader); //assert(data[7] == 0x01); // '00000001' + + // program stream descriptor + program_stream_info_length = mpeg_bits_read16(reader); //(data[8] << 8) | data[9]; + if ((uint32_t)program_stream_info_length + 4 + 2 /*element_stream_map_length*/ > (uint32_t)program_stream_map_length) + return MPEG_ERROR_INVALID_DATA; + + // TODO: parse descriptor + //for (i = 10; i + 2 <= 10 + program_stream_info_length;) + //{ + // // descriptor() + // i += mpeg_elment_descriptor(data + i, 10 + program_stream_info_length - i); + //} + mpeg_bits_skip(reader, program_stream_info_length); // 10 + program_stream_info_length; + + // program element stream + element_stream_map_length = mpeg_bits_read16(reader); + /* Ignore es_map_length, trust psm_length */ + element_stream_map_length = program_stream_map_length - program_stream_info_length - 10; + end = mpeg_bits_tell(reader) + element_stream_map_length; + + while (0 == mpeg_bits_error(reader) + && mpeg_bits_tell(reader) + 4 /*element_stream_info_length*/ <= end + && psm->stream_count < sizeof(psm->streams) / sizeof(psm->streams[0])) + { + cid = mpeg_bits_read8(reader); + sid = mpeg_bits_read8(reader); + element_stream_info_length = mpeg_bits_read16(reader); + if (mpeg_bits_tell(reader) + element_stream_info_length > end) + return MPEG_ERROR_INVALID_DATA; + + stream = psm_fetch(psm, sid); // sid + if (NULL == stream) + continue; + stream->codecid = cid; + stream->sid = sid; + stream->pid = stream->sid; // for ts PID + + off = mpeg_bits_tell(reader); + if (0xFD == stream->sid && 0 == single_extension_stream_flag) + { + if (element_stream_info_length < 3) + return MPEG_ERROR_INVALID_DATA; + //uint8_t pseudo_descriptor_tag = mpeg_bits_read8(reader); + //uint8_t pseudo_descriptor_length = mpeg_bits_read8(reader) + //uint8_t element_stream_id_extension = mpeg_bits_read8(reader) & 0x7F; + //assert((0x80 & data[k + 2]) == 0x80); // '1xxxxxxx' + mpeg_bits_skip(reader, 3); + } + + while (0 == mpeg_bits_error(reader) && mpeg_bits_tell(reader) < off + element_stream_info_length) + { + // descriptor() + mpeg_elment_descriptor(reader); + } + + assert(mpeg_bits_tell(reader) == off + element_stream_info_length); + mpeg_bits_seek(reader, off + element_stream_info_length); // make sure + } + + mpeg_bits_read32(reader); // crc32 + // assert(j+4 == program_stream_map_length+6); + // assert(0 == mpeg_crc32(0xffffffff, data, program_stream_map_length+6)); + assert(0 == mpeg_bits_error(reader)); + assert(end + 4 /*crc32*/ == mpeg_bits_tell(reader)); + return MPEG_ERROR_OK; +} + +#else +size_t psm_read(struct psm_t *psm, const uint8_t* data, size_t bytes) +{ + size_t i, j, k; + struct pes_t* stream; + //uint8_t current_next_indicator; + uint8_t single_extension_stream_flag; + uint16_t program_stream_map_length; + uint16_t program_stream_info_length; + uint16_t element_stream_map_length; + uint16_t element_stream_info_length; + + // Table 2-41 - Program stream map(p79) + assert(0x00==data[0] && 0x00==data[1] && 0x01==data[2] && 0xBC==data[3]); + program_stream_map_length = (data[4] << 8) | data[5]; + if (program_stream_map_length < 3 || bytes < (size_t)program_stream_map_length + 6) + return 0; // invalid data length + + //assert((0x20 & data[6]) == 0x00); // 'xx0xxxxx' + //current_next_indicator = (data[6] >> 7) & 0x01; + single_extension_stream_flag = (data[6] >> 6) & 0x01; + psm->ver = data[6] & 0x1F; + //assert(data[7] == 0x01); // '00000001' + + // program stream descriptor + program_stream_info_length = (data[8] << 8) | data[9]; + if ((size_t)program_stream_info_length + 4 + 2 /*element_stream_map_length*/ > (size_t)program_stream_map_length) + return 0; // TODO: error + + // TODO: parse descriptor + //for (i = 10; i + 2 <= 10 + program_stream_info_length;) + //{ + // // descriptor() + // i += mpeg_elment_descriptor(data + i, 10 + program_stream_info_length - i); + //} + + // program element stream + i = 10 + program_stream_info_length; + element_stream_map_length = (data[i] << 8) | data[i+1]; + /* Ignore es_map_length, trust psm_length */ + element_stream_map_length = program_stream_map_length - program_stream_info_length - 10; + + i += 2; + for(j = i; j + 4/*element_stream_info_length*/ <= i+element_stream_map_length && psm->stream_count < sizeof(psm->streams)/sizeof(psm->streams[0]); j += 4 + element_stream_info_length) + { + element_stream_info_length = (data[j + 2] << 8) | data[j + 3]; + if (j + 4 + element_stream_info_length > i + element_stream_map_length) + return 0; // TODO: error + + stream = psm_fetch(psm, data[j + 1]); // sid + if (NULL == stream) + continue; + stream->codecid = data[j]; + stream->sid = data[j+1]; + stream->pid = stream->sid; // for ts PID + + k = j + 4; + if(0xFD == stream->sid && 0 == single_extension_stream_flag) + { + if(element_stream_info_length < 3) + return 0; // TODO: error +// uint8_t pseudo_descriptor_tag = data[k]; +// uint8_t pseudo_descriptor_length = data[k+1]; +// uint8_t element_stream_id_extension = data[k+2] & 0x7F; + assert((0x80 & data[k+2]) == 0x80); // '1xxxxxxx' + k += 3; + } + + while(k + 2 <= j + 4 + element_stream_info_length) + { + // descriptor() + k += mpeg_elment_descriptor(data+k, j + 4 + element_stream_info_length - k); + } + + assert(k - j - 4 == element_stream_info_length); + } + +// assert(j+4 == program_stream_map_length+6); +// assert(0 == mpeg_crc32(0xffffffff, data, program_stream_map_length+6)); + return program_stream_map_length+6; +} +#endif + +size_t psm_write(const struct psm_t *psm, uint8_t *data) +{ + // Table 2-41 - Program stream map(p79) + + size_t i,j; + uint16_t extlen; + unsigned int crc; + + nbo_w32(data, 0x00000100); + data[3] = PES_SID_PSM; + + // program_stream_map_length 16-bits + //nbo_w16(data+4, 6+4*psm->stream_count+4); + + // current_next_indicator '1' + // single_extension_stream_flag '1' + // reserved '0' + // program_stream_map_version 'xxxxx' + data[6] = 0xc0 | (psm->ver & 0x1F); + + // reserved '0000000' + // marker_bit '1' + data[7] = 0x01; + + extlen = 0; + extlen += (uint16_t)service_extension_descriptor_write(data + 10 + extlen, 32); +#if defined(MPEG_CLOCK_EXTENSION_DESCRIPTOR) + extlen += (uint16_t)clock_extension_descriptor_write(data + 10 + extlen, 32, psm->clock); +#endif + + // program_stream_info_length 16-bits + nbo_w16(data + 8, extlen); // program_stream_info_length = 0 + + // elementary_stream_map_length 16-bits + //nbo_w16(data+10+extlen, psm->stream_count*4); + + j = 12 + extlen; + for(i = 0; i < psm->stream_count; i++) + { + assert(PES_SID_EXTEND != psm->streams[i].sid); + + // stream_type:8 + data[j++] = psm->streams[i].codecid; + // elementary_stream_id:8 + data[j++] = psm->streams[i].sid; + // elementary_stream_info_length:16 + nbo_w16(data+j, psm->streams[i].esinfo_len); + // descriptor() + memcpy(data+j+2, psm->streams[i].esinfo, psm->streams[i].esinfo_len); + + j += 2 + psm->streams[i].esinfo_len; + } + + // elementary_stream_map_length 16-bits + nbo_w16(data + 10 + extlen, (uint16_t)(j - 12 - extlen)); + // program_stream_map_length:16 + nbo_w16(data + 4, (uint16_t)(j-6+4)); // 4-bytes crc32 + + // crc32 + crc = mpeg_crc32(0xffffffff, data, (uint32_t)j); + data[j+3] = (uint8_t)((crc >> 24) & 0xFF); + data[j+2] = (uint8_t)((crc >> 16) & 0xFF); + data[j+1] = (uint8_t)((crc >> 8) & 0xFF); + data[j+0] = (uint8_t)(crc & 0xFF); + + return j+4; +} diff --git a/MediaServer/libmpeg/source/mpeg-sdt.c b/MediaServer/libmpeg/source/mpeg-sdt.c new file mode 100644 index 0000000..352fb43 --- /dev/null +++ b/MediaServer/libmpeg/source/mpeg-sdt.c @@ -0,0 +1,180 @@ +// ETSI EN 300 468 V1.15.1 (2016-03) +// Digital Video Broadcasting (DVB); Specification for Service Information (SI) in DVB systems +// 5.2.3 Service Description Table (SDT) (p27) + +#include "mpeg-ts-internal.h" +#include "mpeg-element-descriptor.h" +#include "mpeg-util.h" +#include +#include + +#define SERVICE_ENCODER "encoder" + +/* +service_description_section(){ + table_id 8 uimsbf + section_syntax_indicator 1 bslbf + reserved_future_use 1 bslbf + reserved 2 bslbf + section_length 12 uimsbf + transport_stream_id 16 uimsbf + reserved 2 bslbf + version_number 5 uimsbf + current_next_indicator 1 bslbf + section_number 8 uimsbf + last_section_number 8 uimsbf + original_network_id 16 uimsbf + reserved_future_use 8 bslbf + for (i=0;i> 7) & 0x01; +// uint32_t zero = (data[1] >> 6) & 0x01; +// uint32_t reserved = (data[1] >> 4) & 0x03; + section_length = ((data[1] & 0x0F) << 8) | data[2]; +// uint32_t transport_stream_id = (data[3] << 8) | data[4]; +// uint32_t reserved2 = (data[5] >> 6) & 0x03; +// uint32_t version_number = (data[5] >> 1) & 0x1F; +// uint32_t current_next_indicator = data[5] & 0x01; +// uint32_t sector_number = data[6]; +// uint32_t last_sector_number = data[7]; +// uint32_t original_network_id = (data[8] << 8) | data[9]; +// uint32_t reserved4 = data[10]; + + if(PAT_TID_SDT != data[0] || section_length + 3 > bytes) + return 0; + + // TODO: version_number change, reload SDT + + assert(bytes >= section_length + 3); // PMT = section_length + 3 + for (i = 11; i + 5 <= section_length + 3 - 4/*CRC32*/ && section_length + 3 <= bytes; i += 5 + n) // 9: follow section_length item + { + sid = (data[i] << 8) | data[i + 1]; + n = ((data[i+3] & 0x0F) << 8) | data[i+4]; + // skip reserved/EIT data[i+2] + if(i + 5 + n > section_length + 3 - 4) + continue; + + pmt = pat_find(pat, sid); + if(NULL == pmt) + continue; // pmt not found + + for(k = i + 5; k + 2 <= i + 5 + n; k += 2 + taglen) + { + // 6.1 Descriptor identification and location + tagid = data[k]; + taglen = data[k + 1]; + + // 6.2.33 Service descriptor (p77) + if(0x48 != tagid || k + taglen > i + 5 + n) + continue; + + //service_type = data[k + 2]; + tagn1 = data[k + 3]; + if(tagn1 >= sizeof(pmt->provider) || k + 3 + tagn1 > i + 5 + n) + continue; + memcpy(pmt->provider, &data[k+4], tagn1); + pmt->provider[tagn1] = 0; + tagn2 = data[k + 4 + tagn1]; + if(tagn2 >= sizeof(pmt->name) || k + 5 + tagn1 + tagn2 > i + 5 + n) + continue; + memcpy(pmt->name, &data[k+5+tagn1], tagn2); + pmt->name[tagn2] = 0; + } + } + + //assert(j+4 == bytes); + //crc = (data[j] << 24) | (data[j+1] << 16) | (data[j+2] << 8) | data[j+3]; +// assert(0 == mpeg_crc32(0xffffffff, data, section_length+3)); + return section_length + 3; +} + +size_t sdt_write(const struct pat_t* pat, uint8_t* data) +{ + size_t i, j; + size_t len, s1, n1, v1; + uint32_t crc = 0; + + n1 = strlen(SERVICE_ENCODER); + v1 = strlen(SERVICE_NAME); + s1 = 3 /*tag*/ + 1 + n1 + 1 + v1; + len = 3 /*nid*/ + s1 + 5 /*service head*/ + 5 + 4; // 5 bytes remain header and 4 bytes crc32 + + // shall not exceed 1021 (0x3FD). + assert(len <= 1021); + assert(len <= TS_PACKET_SIZE - 7); + + data[0] = PAT_TID_SDT; // service_description_section + + // section_syntax_indicator = '1' + // '0' + // reserved '11' + nbo_w16(data + 1, (uint16_t)(0xf000 | len)); + + // transport_stream_id + nbo_w16(data + 3, (uint16_t)pat->tsid); + + // reserved '11' + // version_number 'xxxxx' + // current_next_indicator '1' + data[5] = (uint8_t)(0xC1 | (pat->ver << 1)); + + // section_number/last_section_number + data[6] = 0x00; + data[7] = 0x00; + + // original_network_id + nbo_w16(data + 8, (uint16_t)pat->tsid); + data[10] = 0xFF; // reserved + + j = 11; + // only one + for (i = 0; i < 1; i++) + { + nbo_w16(data + j, (uint16_t)pat->tsid); + data[j + 2] = 0xfc | 0x00; // no EIT + + assert(n1 < 255 && v1 < 255 && len < 255); + nbo_w16(data + j + 3, (uint16_t)(0x8000 | s1)); + + data[j + 5] = 0x48; // tag id + data[j + 6] = (uint8_t)(3 + n1 + v1); // tag len + data[j + 7] = 1; // service type + data[j + 8] = (uint8_t)n1; + memcpy(data + j + 9, SERVICE_NAME, n1); + data[j + 9 + n1] = (uint8_t)v1; + memcpy(data + j + 10 + n1, SERVICE_NAME, v1); + j += 10 + v1 + n1; + } + + // crc32 + crc = mpeg_crc32(0xffffffff, data, (uint32_t)j); + data[j + 3] = (uint8_t)((crc >> 24) & 0xFF); + data[j + 2] = (uint8_t)((crc >> 16) & 0xFF); + data[j + 1] = (uint8_t)((crc >> 8) & 0xFF); + data[j + 0] = (uint8_t)(crc & 0xFF); + return j + 4; +} diff --git a/MediaServer/libmpeg/source/mpeg-system-header.c b/MediaServer/libmpeg/source/mpeg-system-header.c new file mode 100644 index 0000000..e7d5d6e --- /dev/null +++ b/MediaServer/libmpeg/source/mpeg-system-header.c @@ -0,0 +1,163 @@ +#include "mpeg-ps-internal.h" +#include "mpeg-util.h" +#include + +// 2.5.3.5 System header (p79) +// Table 2-40 - Program stream system header +#if 1 +int system_header_read(struct ps_system_header_t* h, struct mpeg_bits_t* reader) +{ + uint8_t v8; + uint16_t v16; + size_t i, len, end; + + len = mpeg_bits_read16(reader); + end = mpeg_bits_tell(reader) + len; + if (mpeg_bits_error(reader) || end > mpeg_bits_length(reader)) + return MPEG_ERROR_NEED_MORE_DATA; + + //assert((0x80 & data[6]) == 0x80); // '1xxxxxxx' + //assert((0x01 & data[8]) == 0x01); // 'xxxxxxx1' + h->rate_bound = (mpeg_bits_read8(reader) & 0x7F) << 15; + h->rate_bound |= mpeg_bits_read15(reader); + + v8 = mpeg_bits_read8(reader); + h->audio_bound = (v8 >> 2) & 0x3F; + h->fixed_flag = (v8 >> 1) & 0x01; + h->CSPS_flag = (v8 >> 0) & 0x01; + + v8 = mpeg_bits_read8(reader); + assert((0x20 & v8) == 0x20); // 'xx1xxxxx' + h->system_audio_lock_flag = (v8 >> 7) & 0x01; + h->system_video_lock_flag = (v8 >> 6) & 0x01; + h->video_bound = v8 & 0x1F; + + // assert((0x7F & data[11]) == 0x00); // 'x0000000' + h->packet_rate_restriction_flag = (mpeg_bits_read8(reader) >> 7) & 0x01; + + for (i = 0; 0 == mpeg_bits_error(reader) && mpeg_bits_tell(reader) + 1 < end && i < sizeof(h->streams) / sizeof(h->streams[0]); i++) + { + v8 = mpeg_bits_read8(reader); + if ((v8 & 0x80) != 0x80) + break; + + h->streams[i].stream_id = v8; + if (h->streams[i].stream_id == PES_SID_EXTENSION) // '10110111' + { + v8 = mpeg_bits_read8(reader); assert(v8 == 0xC0); // '11000000' + h->streams[i].stream_id = mpeg_bits_read8(reader) & 0x7F; + v8 = mpeg_bits_read8(reader); assert(v8 == 0xB6); // '10110110' + } + + v16 = mpeg_bits_read16(reader); + assert((v16 & 0xC000) == 0xC000); // '11xxxxxx' + h->streams[i].buffer_bound_scale = (v16 >> 13) & 0x01; + h->streams[i].buffer_size_bound = v16 & 0x1FFF; + } + + assert(0 == mpeg_bits_error(reader)); + //assert(end == mpeg_bits_tell(reader)); + return MPEG_ERROR_OK; +} + +#else +size_t system_header_read(struct ps_system_header_t *h, const uint8_t* data, size_t bytes) +{ + size_t i, j; + size_t len; + + if (bytes < 12) return 0; + + assert(0x00 == data[0] && 0x00 == data[1] && 0x01 == data[2] && PES_SID_SYS == data[3]); + len = (data[4] << 8) | data[5]; + if(len + 6 > bytes) + { + assert(0); + return 0; + } + + assert((0x80 & data[6]) == 0x80); // '1xxxxxxx' + assert((0x01 & data[8]) == 0x01); // 'xxxxxxx1' + h->rate_bound = ((data[6] & 0x7F) << 15) | (data[7] << 7) | ((data[8] >> 1) & 0x7F); + + h->audio_bound = (data[9] >> 2) & 0x3F; + h->fixed_flag = (data[9] >> 1) & 0x01; + h->CSPS_flag = (data[9] >> 0) & 0x01; + + assert((0x20 & data[10]) == 0x20); // 'xx1xxxxx' + h->system_audio_lock_flag = (data[10] >> 7) & 0x01; + h->system_video_lock_flag = (data[10] >> 6) & 0x01; + h->video_bound = data[10] & 0x1F; + + // assert((0x7F & data[11]) == 0x00); // 'x0000000' + h->packet_rate_restriction_flag = (data[11] >> 7) & 0x01; + + i = 12; + for (j = 0; (data[i] & 0x80) == 0x80 && j < sizeof(h->streams) / sizeof(h->streams[0]) && i < bytes; j++) + { + h->streams[j].stream_id = data[i++]; + if (h->streams[j].stream_id == PES_SID_EXTENSION) // '10110111' + { + assert(data[i] == 0xC0); // '11000000' + assert((data[i + 1] & 80) == 0); // '1xxxxxxx' + h->streams[j].stream_id = (h->streams[j].stream_id << 7) | (data[i + 1] & 0x7F); + assert(data[i + 2] == 0xB6); // '10110110' + i += 3; + } + + assert((data[i] & 0xC0) == 0xC0); // '11xxxxxx' + h->streams[j].buffer_bound_scale = (data[i] >> 5) & 0x01; + h->streams[j].buffer_size_bound = (data[i] & 0x1F) | data[i + 1]; + i += 2; + } + + return len + 4 + 2; +} +#endif + +size_t system_header_write(const struct ps_system_header_t *h, uint8_t *data) +{ + size_t i, j; + + // system_header_start_code + nbo_w32(data, 0x000001BB); + + // header length + //put16(data + 4, 6 + h->stream_count*3); + + // rate_bound + // 1xxxxxxx xxxxxxxx xxxxxxx1 + data[6] = 0x80 | ((h->rate_bound >> 15) & 0x7F); + data[7] = (h->rate_bound >> 7) & 0xFF; + data[8] = 0x01 | ((h->rate_bound & 0x7F) << 1); + + // 6-audio_bound + 1-fixed_flag + 1-CSPS_flag + data[9] = ((h->audio_bound & 0x3F) << 2) | ((h->fixed_flag & 0x01) << 1) | (h->CSPS_flag & 0x01); + + // 1-system_audio_lock_flag + 1-system_video_lock_flag + 1-maker + 5-video_bound + data[10] = 0x20 | ((h->system_audio_lock_flag & 0x01) << 7) | ((h->system_video_lock_flag & 0x01) << 6) | (h->video_bound & 0x1F); + + // 1-packet_rate_restriction_flag + 7-reserved + data[11] = 0x7F | ((h->packet_rate_restriction_flag & 0x01) << 7); + + i = 12; + for (j = 0; j < h->stream_count; j++) + { + data[i++] = (uint8_t)h->streams[j].stream_id; + if (PES_SID_EXTENSION == h->streams[j].stream_id) // '10110111' + { + data[i++] = 0xD0; // '11000000' + data[i++] = h->streams[j].stream_extid & 0x7F; // '0xxxxxxx' + data[i++] = 0xB6; // '10110110' + } + + // '11' + 1-P-STD_buffer_bound_scale + 13-P-STD_buffer_size_bound + // '11xxxxxx xxxxxxxx' + data[i++] = 0xC0 | ((h->streams[j].buffer_bound_scale & 0x01) << 5) | ((h->streams[j].buffer_size_bound >> 8) & 0x1F); + data[i++] = h->streams[j].buffer_size_bound & 0xFF; + } + + // header length + nbo_w16(data + 4, (uint16_t)(i - 6)); + return i; +} diff --git a/MediaServer/libmpeg/source/mpeg-ts-dec.c b/MediaServer/libmpeg/source/mpeg-ts-dec.c new file mode 100644 index 0000000..c0e8637 --- /dev/null +++ b/MediaServer/libmpeg/source/mpeg-ts-dec.c @@ -0,0 +1,357 @@ +// ITU-T H.222.0(06/2012) +// Information technology - Generic coding of moving pictures and associated audio information: Systems +// 2.4.3.1 Transport stream(p34) + +#include "mpeg-pes-internal.h" +#include "mpeg-ts-internal.h" +#include "mpeg-util.h" +#include "mpeg-ts.h" +#include +#include +#include +#include +#include + +struct ts_demuxer_t +{ + struct pat_t pat; + + ts_demuxer_onpacket onpacket; + void* param; + + struct ts_demuxer_notify_t notify; + void* notify_param; +}; + +static void ts_demuxer_notify(struct ts_demuxer_t* ts, const struct pmt_t* pmt); + +static uint32_t adaptation_filed_read(struct ts_adaptation_field_t *adp, const uint8_t* data, size_t bytes) +{ + // 2.4.3.4 Adaptation field + // Table 2-6 + uint32_t i = 0; + uint32_t j = 0; + + assert(bytes <= TS_PACKET_SIZE); + adp->adaptation_field_length = data[i++]; + if(adp->adaptation_field_length > 0) + { + adp->discontinuity_indicator = (data[i] >> 7) & 0x01; + adp->random_access_indicator = (data[i] >> 6) & 0x01; + adp->elementary_stream_priority_indicator = (data[i] >> 5) & 0x01; + adp->PCR_flag = (data[i] >> 4) & 0x01; + adp->OPCR_flag = (data[i] >> 3) & 0x01; + adp->splicing_point_flag = (data[i] >> 2) & 0x01; + adp->transport_private_data_flag = (data[i] >> 1) & 0x01; + adp->adaptation_field_extension_flag = (data[i] >> 0) & 0x01; + i++; + + if(adp->PCR_flag && i + 6 <= adp->adaptation_field_length + 1) + { + adp->program_clock_reference_base = ((uint64_t)data[i] << 25) | ((uint64_t)data[i+1] << 17) | ((uint64_t)data[i+2] << 9) | ((uint64_t)data[i+3] << 1) | ((data[i+4] >> 7) & 0x01); + adp->program_clock_reference_extension = ((data[i+4] & 0x01) << 8) | data[i+5]; + + i += 6; + } + + if(adp->OPCR_flag && i + 6 <= adp->adaptation_field_length + 1) + { + adp->original_program_clock_reference_base = (((uint64_t)data[i]) << 25) | ((uint64_t)data[i+1] << 17) | ((uint64_t)data[i+2] << 9) | ((uint64_t)data[i+3] << 1) | ((data[i+4] >> 7) & 0x01); + adp->original_program_clock_reference_extension = ((data[i+4] & 0x01) << 1) | data[i+5]; + + i += 6; + } + + if(adp->splicing_point_flag && i + 1 <= adp->adaptation_field_length + 1) + { + adp->splice_countdown = data[i++]; + } + + if(adp->transport_private_data_flag && i + 1 <= adp->adaptation_field_length + 1) + { + adp->transport_private_data_length = data[i++]; + for(j = 0; j < adp->transport_private_data_length; j++) + { +// uint8_t transport_private_data = data[i+j]; + } + + i += adp->transport_private_data_length; + } + + if(adp->adaptation_field_extension_flag && i + 2 <= adp->adaptation_field_length + 1) + { + //uint8_t reserved; + adp->adaptation_field_extension_length = data[i++]; + adp->ltw_flag = (data[i] >> 7) & 0x01; + adp->piecewise_rate_flag = (data[i] >> 6) & 0x01; + adp->seamless_splice_flag = (data[i] >> 5) & 0x01; + //reserved = data[i] & 0x1F; + + i++; + if(adp->ltw_flag && i + 2 <= adp->adaptation_field_length + 1) + { +// uint8_t ltw_valid_flag = (data[i] >> 7) & 0x01; +// uint16_t ltw_offset = ((data[i] & 0x7F) << 8) | data[i+1]; + i += 2; + } + + if(adp->piecewise_rate_flag && i + 3 <= adp->adaptation_field_length + 1) + { +// uint32_t piecewise_rate = ((data[i] & 0x3F) << 16) | (data[i+1] << 8) | data[i+2]; + i += 3; + } + + if(adp->seamless_splice_flag && i + 5 <= adp->adaptation_field_length + 1) + { +// uint8_t Splice_type = (data[i] >> 4) & 0x0F; +// uint32_t DTS_next_AU = (((data[i] >> 1) & 0x07) << 30) | (data[i+1] << 22) | (((data[i+2] >> 1) & 0x7F) << 15) | (data[i+3] << 7) | ((data[i+4] >> 1) & 0x7F); + + i += 5; + } + + // reserved byte + } + + // stuffing byte + } + + return adp->adaptation_field_length + 1; +} + +#define TS_IS_SYNC_BYTE(data) (data[0] == TS_SYNC_BYTE) +#define TS_TRANSPORT_ERROR_INDICATOR(data) (data[1] & 0x80) +#define TS_PAYLOAD_UNIT_START_INDICATOR(data) (data[1] & 0x40) +#define TS_TRANSPORT_PRIORITY(data) (data[1] & 0x20) + +int ts_demuxer_flush(struct ts_demuxer_t* ts) +{ + uint32_t i, j; + size_t consume; + for (i = 0; i < ts->pat.pmt_count; i++) + { + for (j = 0; j < ts->pat.pmts[i].stream_count; j++) + { + struct pes_t* pes = &ts->pat.pmts[i].streams[j]; + if (pes->pkt.size < 5) + continue; + + if (PSI_STREAM_H264 == pes->codecid) + { + const uint8_t aud[] = {0,0,0,1,0x09,0xf0}; + pes_packet(&pes->pkt, pes, aud, sizeof(aud), &consume, 0, ts->onpacket, ts->param); + } + else if (PSI_STREAM_H265 == pes->codecid) + { + const uint8_t aud[] = {0,0,0,1,0x46,0x01,0x50}; + pes_packet(&pes->pkt, pes, aud, sizeof(aud), &consume, 0, ts->onpacket, ts->param); + } + else if (PSI_STREAM_H266 == pes->codecid) + { + const uint8_t aud[] = { 0,0,0,1,0x00,0xA1,0x18 }; + pes_packet(&pes->pkt, pes, aud, sizeof(aud), &consume, 0, ts->onpacket, ts->param); + } + else + { + //assert(0); + pes_packet(&pes->pkt, pes, NULL, 0, &consume, 0, ts->onpacket, ts->param); + } + } + } + return 0; +} + +int ts_demuxer_input(struct ts_demuxer_t* ts, const uint8_t* data, size_t bytes) +{ + int r = 0; + uint32_t i, j, k; + uint32_t PID; + size_t consume; + unsigned int count, ver; + struct mpeg_bits_t reader; + struct ts_packet_header_t pkhd; + + // 2.4.3 Specification of the transport stream syntax and semantics + // Transport stream packets shall be 188 bytes long. + assert(188 == bytes); + + // 2.4.3.2 Transport stream packet layer + // Table 2-2 + memset(&pkhd, 0, sizeof(pkhd)); + assert(0x47 == data[0]); // sync_byte + PID = ((data[1] << 8) | data[2]) & 0x1FFF; + pkhd.transport_error_indicator = (data[1] >> 7) & 0x01; + pkhd.payload_unit_start_indicator = (data[1] >> 6) & 0x01; + pkhd.transport_priority = (data[1] >> 5) & 0x01; + pkhd.transport_scrambling_control = (data[3] >> 6) & 0x03; + pkhd.adaptation_field_control = (data[3] >> 4) & 0x03; + pkhd.continuity_counter = data[3] & 0x0F; + +// printf("-----------------------------------------------\n"); +// printf("PID[%u]: Error: %u, Start:%u, Priority:%u, Scrambler:%u, AF: %u, CC: %u\n", PID, pkhd.transport_error_indicator, pkhd.payload_unit_start_indicator, pkhd.transport_priority, pkhd.transport_scrambling_control, pkhd.adaptation_field_control, pkhd.continuity_counter); + + i = 4; + if(0x02 & pkhd.adaptation_field_control) + { + i += adaptation_filed_read(&pkhd.adaptation, data + 4, bytes - 4); + + if(pkhd.adaptation.adaptation_field_length > 0 && pkhd.adaptation.PCR_flag) + { + //int64_t t; + //t = pkhd.adaptation.program_clock_reference_base / 90L; // ms; + //printf("pcr: %02d:%02d:%02d.%03d - %" PRId64 "/%u\n", (int)(t / 3600000), (int)(t % 3600000)/60000, (int)((t/1000) % 60), (int)(t % 1000), pkhd.adaptation.program_clock_reference_base, pkhd.adaptation.program_clock_reference_extension); + } + + assert(i <= bytes); + if (i + (pkhd.payload_unit_start_indicator ? 1 : 0) >= bytes) + return 0; // ignore + } + + if(0x01 & pkhd.adaptation_field_control) + { + if(TS_PID_PAT == PID) + { + if(pkhd.payload_unit_start_indicator) + i += 1; // pointer 0x00 + + // TODO: PAT lost + pat_read(&ts->pat, data + i, bytes - i); + } + else if(TS_PID_SDT == PID) + { + if(pkhd.payload_unit_start_indicator) + i += 1; // pointer 0x00 + sdt_read(&ts->pat, data + i, bytes - i); + } + else + { + for(j = 0; j < ts->pat.pmt_count; j++) + { + if(PID == ts->pat.pmts[j].pid) + { + // TODO: PMT lost + if(pkhd.payload_unit_start_indicator) + i += 1; // pointer 0x00 + + ver = ts->pat.pmts[j].ver; + count = ts->pat.pmts[j].stream_count; + pmt_read(&ts->pat.pmts[j], data + i, bytes - i); + if(ver != ts->pat.pmts[j].ver || count != ts->pat.pmts[j].stream_count) + ts_demuxer_notify(ts, &ts->pat.pmts[j]); + break; + } + else + { + for (k = 0; k < ts->pat.pmts[j].stream_count; k++) + { + struct pes_t* pes = &ts->pat.pmts[j].streams[k]; + if (PID != pes->pid) + continue; + + pes->flags |= ((ts->pat.pmts[j].streams[k].cc + 1) % 16) != (uint8_t)pkhd.continuity_counter ? (MPEG_FLAG_PACKET_CORRUPT | MPEG_FLAG_PACKET_LOST) : 0; + ts->pat.pmts[j].streams[k].cc = (uint8_t)pkhd.continuity_counter; + + if (pkhd.payload_unit_start_indicator) + { + size_t s; + mpeg_bits_init(&reader, data + i, bytes - i); + s = mpeg_bits_readn(&reader, 3); + pes->sid = mpeg_bits_read8(&reader); + if (MPEG_ERROR_OK != pes_read_header(pes, &reader) || s != 0x000001) + { + assert(0); + return 0; // ignore + } + i += (uint32_t)mpeg_bits_tell(&reader); + + pes->flags = (pes->flags & MPEG_FLAG_PACKET_CORRUPT) ? MPEG_FLAG_PACKET_LOST : 0; + pes->flags |= pes->data_alignment_indicator ? MPEG_FLAG_IDR_FRAME : 0; + pes->have_pes_header = 1; + } + else if (!pes->have_pes_header) + { + return 0; // ignore, don't have pes header yet + } + + r = pes_packet(&pes->pkt, pes, data + i, bytes - i, &consume, pkhd.payload_unit_start_indicator, ts->onpacket, ts->param); + pes->have_pes_header = (r || (0 == pes->pkt.size && pes->len > 0)) ? 0 : 1; // packet completed + return r; // find stream + } + } // PMT handler + } + } // PAT handler + } + + return r; +} + +static inline int mpeg_ts_is_idr_first_packet(const void* packet, int bytes) +{ + const unsigned char *data; + struct ts_packet_header_t pkhd; + int payload_unit_start_indicator; + + memset(&pkhd, 0, sizeof(pkhd)); + + data = (const unsigned char *)packet; + payload_unit_start_indicator = data[1] & 0x40; + pkhd.adaptation_field_control = (data[3] >> 4) & 0x03; + pkhd.continuity_counter = data[3] & 0x0F; + + if(0x02 == pkhd.adaptation_field_control || 0x03 == pkhd.adaptation_field_control) + { + adaptation_filed_read(&pkhd.adaptation, data + 4, bytes - 4); + } + + return (payload_unit_start_indicator && pkhd.adaptation.random_access_indicator) ? 1 : 0; +} + +struct ts_demuxer_t* ts_demuxer_create(ts_demuxer_onpacket onpacket, void* param) +{ + struct ts_demuxer_t* ts; + ts = calloc(1, sizeof(struct ts_demuxer_t)); + if (!ts) + return NULL; + + ts->onpacket = onpacket; + ts->param = param; + return ts; +} + +int ts_demuxer_destroy(struct ts_demuxer_t* ts) +{ + pat_clear(&ts->pat); + free(ts); + return 0; +} + +int ts_demuxer_getservice(struct ts_demuxer_t* ts, int program, char* provider, int nprovider, char* name, int nname) +{ + struct pmt_t* pmt; + pmt = pat_find(&ts->pat, (uint16_t)program); + if(NULL == pmt) + return -1; + + snprintf(provider, nprovider, "%s", pmt->provider); + snprintf(name, nname, "%s", pmt->name); + return 0; +} + +void ts_demuxer_set_notify(struct ts_demuxer_t* ts, struct ts_demuxer_notify_t* notify, void* param) +{ + ts->notify_param = param; + memcpy(&ts->notify, notify, sizeof(ts->notify)); +} + +static void ts_demuxer_notify(struct ts_demuxer_t* ts, const struct pmt_t* pmt) +{ + unsigned int i; + const struct pes_t* pes; + if (!ts->notify.onstream) + return; + + for (i = 0; i < pmt->stream_count; i++) + { + pes = &pmt->streams[i]; + ts->notify.onstream(ts->notify_param, pes->pid, pes->codecid, pes->esinfo, pes->esinfo_len, i + 1 >= pmt->stream_count ? 1 : 0); + } +} diff --git a/MediaServer/libmpeg/source/mpeg-ts-enc.c b/MediaServer/libmpeg/source/mpeg-ts-enc.c new file mode 100644 index 0000000..20c7170 --- /dev/null +++ b/MediaServer/libmpeg/source/mpeg-ts-enc.c @@ -0,0 +1,554 @@ +// ITU-T H.222.0(06/2012) +// Information technology - Generic coding of moving pictures and associated audio information: Systems +// 2.4.3.1 Transport stream(p34) + +#include "mpeg-ts-internal.h" +#include "mpeg-util.h" +#include "mpeg-ts.h" +#include +#include +#include +#include + +#define PCR_DELAY 0 //(700 * 90) // 700ms +#define PAT_PERIOD (400 * 90) // 500ms +#define PAT_CYCLE 50 // 50fps(audio + video) + +#define TS_HEADER_LEN 4 // 1-bytes sync byte + 2-bytes PID + 1-byte CC +#define PES_HEADER_LEN 6 // 3-bytes packet_start_code_prefix + 1-byte stream_id + 2-bytes PES_packet_length + +#define TS_PAYLOAD_UNIT_START_INDICATOR 0x40 + +// adaptation flags +#define AF_FLAG_PCR 0x10 +#define AF_FLAG_RANDOM_ACCESS_INDICATOR 0x40 // random_access_indicator + +typedef struct _mpeg_ts_enc_context_t +{ + struct pat_t pat; + int h26x_with_aud; + + int64_t sdt_period; + int64_t pat_period; + int64_t pcr_period; + int64_t pcr_clock; // last pcr time + + int pat_cycle; + uint16_t pid; + + struct mpeg_ts_func_t func; + void* param; + + uint8_t payload[1024]; // maximum PAT/PMT payload length +} mpeg_ts_enc_context_t; + +static int mpeg_ts_write_section_header(const mpeg_ts_enc_context_t *ts, int pid, unsigned int* cc, const void* payload, size_t len) +{ + int r; + uint8_t *data = NULL; + data = ts->func.alloc(ts->param, TS_PACKET_SIZE); + if(!data) return -ENOMEM; + + assert(len < TS_PACKET_SIZE - 5); // TS-header + pointer + + // TS Header + + // sync_byte + data[0] = 0x47; + // transport_error_indicator = 0 + // payload_unit_start_indicator = 1 + // transport_priority = 0 + data[1] = 0x40 | ((pid >> 8) & 0x1F); + data[2] = pid & 0xFF; + // transport_scrambling_control = 0x00 + // adaptation_field_control = 0x01-No adaptation_field, payload only, 0x03-adaptation and payload + data[3] = 0x10 | (*cc & 0x0F); + *cc = (*cc + 1) % 16; // update continuity_counter + +// // Adaptation +// if(len < TS_PACKET_SIZE - 5) +// { +// data[3] |= 0x20; // with adaptation +// data[4] = TS_PACKET_SIZE - len - 5 - 1; // 4B-Header + 1B-pointer + 1B-self +// if(data[4] > 0) +// { +// // adaptation +// data[5] = 0; // no flag +// memset(data+6, 0xFF, data[4]-1); +// } +// } + + // pointer (payload_unit_start_indicator==1) + //data[TS_PACKET_SIZE-len-1] = 0x00; + data[4] = 0x00; + + // TS Payload + //memmove(data + TS_PACKET_SIZE - len, payload, len); + memmove(data + 5, payload, len); + memset(data+5+len, 0xff, TS_PACKET_SIZE-len-5); + + r = ts->func.write(ts->param, data, TS_PACKET_SIZE); + ts->func.free(ts->param, data); + return r; +} + +static uint8_t* mpeg_ts_write_aud(const mpeg_ts_enc_context_t* tsctx, const struct pes_t* stream, const uint8_t* payload, size_t bytes, uint8_t* p) +{ + if (PSI_STREAM_H264 == stream->codecid && !tsctx->h26x_with_aud && !mpeg_h264_start_with_access_unit_delimiter(payload, bytes)) + { + // 2.14 Carriage of Rec. ITU-T H.264 | ISO/IEC 14496-10 video + // Each AVC access unit shall contain an access unit delimiter NAL Unit + nbo_w32(p, 0x00000001); + p[4] = 0x09; // AUD + p[5] = 0xF0; // any slice type (0xe) + rbsp stop one bit + p += 6; + } + else if (PSI_STREAM_H265 == stream->codecid && !tsctx->h26x_with_aud && !mpeg_h265_start_with_access_unit_delimiter(payload, bytes)) + { + // 2.17 Carriage of HEVC + // Each HEVC access unit shall contain an access unit delimiter NAL unit. + nbo_w32(p, 0x00000001); + p[4] = 0x46; // 35-AUD_NUT + p[5] = 0x01; + p[6] = 0x50; // B&P&I (0x2) + rbsp stop one bit + p += 7; + } + else if (PSI_STREAM_H266 == stream->codecid && !tsctx->h26x_with_aud && !mpeg_h266_start_with_access_unit_delimiter(payload, bytes)) + { + // 2.23 Carriage of VVC + // Each VVC access unit shall contain an access unit delimiter NAL unit + nbo_w32(p, 0x00000001); + p[4] = 0x00; // 20-AUD_NUT + p[5] = 0xA1; + p[6] = 0x28; // B&P&I (0x2) + rbsp stop one bit + p += 7; + } + + return p; +} + +static int ts_write_pes(mpeg_ts_enc_context_t *tsctx, const struct pmt_t* pmt, struct pes_t *stream, const uint8_t* payload, size_t bytes) +{ + // 2.4.3.6 PES packet + // Table 2-21 + + int r = 0; + size_t len = 0; + int start = 1; // first packet + uint8_t *p = NULL; + uint8_t *data = NULL; + uint8_t *header = NULL; + + while(0 == r && bytes > 0) + { + data = tsctx->func.alloc(tsctx->param, TS_PACKET_SIZE); + if(!data) return -ENOMEM; + + // TS Header + data[0] = 0x47; // sync_byte + data[1] = 0x00 | ((stream->pid >>8) & 0x1F); + data[2] = stream->pid & 0xFF; + data[3] = 0x10 | (stream->cc & 0x0F); // no adaptation, payload only + data[4] = 0; // clear adaptation length + data[5] = 0; // clear adaptation flags + + stream->cc = (stream->cc + 1) % 16; + + // 2.7.2 Frequency of coding the program clock reference + // http://www.bretl.com/mpeghtml/SCR.HTM + // the maximum between PCRs is 100ms. + if(start && stream->pid == pmt->PCR_PID) + { + data[3] |= 0x20; // +AF + data[5] |= AF_FLAG_PCR; // +PCR_flag + } + + // random_access_indicator + if(start && stream->data_alignment_indicator && PTS_NO_VALUE != stream->pts) + { + //In the PCR_PID the random_access_indicator may only be set to '1' + //in a transport stream packet containing the PCR fields. + data[3] |= 0x20; // +AF + data[5] |= AF_FLAG_RANDOM_ACCESS_INDICATOR; // +random_access_indicator + } + + if(data[3] & 0x20) + { + data[4] = 1; // 1-byte flag + + if(data[5] & AF_FLAG_PCR) // PCR_flag + { + int64_t pcr = 0; + pcr = (PTS_NO_VALUE==stream->dts) ? stream->pts : stream->dts; + // timebase 90kHz -> 27MHz + pcr_write(data + 6, (pcr - PCR_DELAY) * 300); // TODO: delay??? + data[4] += 6; // 6-PCR + } + + header = data + TS_HEADER_LEN + 1 + data[4]; // 4-TS + 1-AF-Len + AF-Payload + } + else + { + header = data + TS_HEADER_LEN; + } + + p = header; + + // PES header + if(start) + { + data[1] |= TS_PAYLOAD_UNIT_START_INDICATOR; // payload_unit_start_indicator + + p += pes_write_header(stream, header, TS_PACKET_SIZE - (header - data)); + p = mpeg_ts_write_aud(tsctx, stream, payload, bytes, p); + + // PES_packet_length = PES-Header + Payload-Size + // A value of 0 indicates that the PES packet length is neither specified nor bounded + // and is allowed only in PES packets whose payload consists of bytes from a + // video elementary stream contained in transport stream packets + if((p - header - PES_HEADER_LEN) + bytes > 0xFFFF) + nbo_w16(header + 4, 0); // 2.4.3.7 PES packet => PES_packet_length + else + nbo_w16(header + 4, (uint16_t)((p - header - PES_HEADER_LEN) + bytes)); + } + + len = p - data; // TS + PES header length + if(len + bytes < TS_PACKET_SIZE) + { + // stuffing_len = TS_PACKET_SIZE - (len + bytes) + + // move pes header + if(p - header > 0) + { + assert(start); + memmove(data + (TS_PACKET_SIZE - bytes - (p - header)), header, p - header); + } + + // adaptation + if(data[3] & 0x20) // has AF? + { + assert(0 != data[5] && data[4] > 0); + memset(data + TS_HEADER_LEN + 1 + data[4], 0xFF, TS_PACKET_SIZE - (len + bytes)); + data[4] += (uint8_t)(TS_PACKET_SIZE - (len + bytes)); + } + else + { + assert(len == (size_t)(p - header) + TS_HEADER_LEN); + data[3] |= 0x20; // +AF + data[4] = (uint8_t)(TS_PACKET_SIZE - (len + bytes) - 1/*AF length*/); + if (data[4] > 0) data[5] = 0; // no flag + if (data[4] > 1) memset(data + 6, 0xFF, TS_PACKET_SIZE - (len + bytes) - 2); + } + len = bytes; + + p = data + 5 + data[4] + (p - header); + } + else + { + len = TS_PACKET_SIZE - len; + } + + // payload + memcpy(p, payload, len); + + payload += len; + bytes -= len; + start = 0; + + // send with TS-header + r = tsctx->func.write(tsctx->param, data, TS_PACKET_SIZE); + tsctx->func.free(tsctx->param, data); + } + + return r; +} + +static struct pes_t *mpeg_ts_find(mpeg_ts_enc_context_t *ts, int pid, struct pmt_t** pmt) +{ + size_t i, j; + struct pes_t* stream; + + for (i = 0; i < ts->pat.pmt_count; i++) + { + *pmt = &ts->pat.pmts[i]; + for (j = 0; j < (*pmt)->stream_count; j++) + { + stream = &(*pmt)->streams[j]; + if (pid == (int)stream->pid) + return stream; + } + } + + return NULL; +} + +int mpeg_ts_write(void* ts, int pid, int flags, int64_t pts, int64_t dts, const void* data, size_t bytes) +{ + int r = 0; + size_t i, n; + struct pmt_t *pmt = NULL; + struct pes_t *stream = NULL; + mpeg_ts_enc_context_t *tsctx; + + tsctx = (mpeg_ts_enc_context_t*)ts; + stream = mpeg_ts_find(tsctx, pid, &pmt); + if (NULL == stream) + return -ENOENT; // not found + + stream->pts = pts; + stream->dts = dts; + stream->data_alignment_indicator = (flags & MPEG_FLAG_IDR_FRAME) ? 1 : 0; // idr frame + tsctx->h26x_with_aud = (flags & MPEG_FLAG_H264_H265_WITH_AUD) ? 1 : 0; + // set PCR_PID + //assert(1 == tsctx->pat.pmt_count); + if (0x1FFF == pmt->PCR_PID || (PES_SID_VIDEO == (stream->sid & PES_SID_VIDEO) && pmt->PCR_PID != stream->pid)) + { + pmt->PCR_PID = stream->pid; + tsctx->pat_period = 0; + tsctx->pat_cycle = 0; + } + + if (pmt->PCR_PID == stream->pid) + ++tsctx->pcr_clock; + + // Add PAT and PMT for video IDR frame + if(0 == ++tsctx->pat_cycle % PAT_CYCLE || 0 == tsctx->pat_period || tsctx->pat_period + PAT_PERIOD <= dts || (PES_SID_VIDEO == (stream->sid & PES_SID_VIDEO) && (flags & MPEG_FLAG_IDR_FRAME))) + { + tsctx->pat_cycle = 0; + tsctx->pat_period = dts; + + if (0 == tsctx->sdt_period) + { + // SDT + tsctx->sdt_period = dts; + n = sdt_write(&tsctx->pat, tsctx->payload); + r = mpeg_ts_write_section_header(ts, TS_PID_SDT, &tsctx->pat.cc /*fixme*/ , tsctx->payload, n); + if (0 != r) return r; + } + + // PAT(program_association_section) + n = pat_write(&tsctx->pat, tsctx->payload); + r = mpeg_ts_write_section_header(ts, TS_PID_PAT, &tsctx->pat.cc, tsctx->payload, n); // PID = 0x00 program association table + if (0 != r) return r; + + // PMT(Transport stream program map section) + for(i = 0; i < tsctx->pat.pmt_count; i++) + { + n = pmt_write(&tsctx->pat.pmts[i], tsctx->payload); + r = mpeg_ts_write_section_header(ts, tsctx->pat.pmts[i].pid, &tsctx->pat.pmts[i].cc, tsctx->payload, n); + if (0 != r) return r; + } + } + + return ts_write_pes(tsctx, pmt, stream, data, bytes); +} + +void* mpeg_ts_create(const struct mpeg_ts_func_t *func, void* param) +{ + mpeg_ts_enc_context_t *tsctx = NULL; + + assert(func); + tsctx = (mpeg_ts_enc_context_t *)calloc(1, sizeof(mpeg_ts_enc_context_t)); + if(!tsctx) + return NULL; + + mpeg_ts_reset(tsctx); + + tsctx->pat.tsid = 1; + tsctx->pat.ver = 0x00; + tsctx->pat.cc = 0; + tsctx->pid = 0x100; + + //tsctx->pat.pmt_count = 1; // only one program in ts + //tsctx->pat.pmts[0].pid = 0x100; + //tsctx->pat.pmts[0].pn = 1; + //tsctx->pat.pmts[0].ver = 0x00; + //tsctx->pat.pmts[0].cc = 0; + //tsctx->pat.pmts[0].pminfo_len = 0; + //tsctx->pat.pmts[0].pminfo = NULL; + //tsctx->pat.pmts[0].PCR_PID = 0x1FFF; // 0x1FFF-don't set PCR + + //tsctx->pat.pmts[0].stream_count = 2; // H.264 + AAC + //tsctx->pat.pmts[0].streams[0].pid = 0x101; + //tsctx->pat.pmts[0].streams[0].sid = PES_SID_AUDIO; + //tsctx->pat.pmts[0].streams[0].codecid = PSI_STREAM_AAC; + //tsctx->pat.pmts[0].streams[1].pid = 0x102; + //tsctx->pat.pmts[0].streams[1].sid = PES_SID_VIDEO; + //tsctx->pat.pmts[0].streams[1].codecid = PSI_STREAM_H264; + + memcpy(&tsctx->func, func, sizeof(tsctx->func)); + tsctx->param = param; + return tsctx; +} + +int mpeg_ts_destroy(void* ts) +{ + mpeg_ts_enc_context_t *tsctx; + tsctx = (mpeg_ts_enc_context_t*)ts; + + pat_clear(&tsctx->pat); + free(tsctx); + return 0; +} + +int mpeg_ts_reset(void* ts) +{ + mpeg_ts_enc_context_t *tsctx; + tsctx = (mpeg_ts_enc_context_t*)ts; +// tsctx->sdt_period = 0; + tsctx->pat_period = 0; + tsctx->pcr_period = 80 * 90; // 100ms maximum + tsctx->pcr_clock = 0; + tsctx->pat_cycle = 0; + return 0; +} + +int mpeg_ts_add_program(void* ts, uint16_t pn, const void* info, int bytes) +{ + unsigned int i; + struct pmt_t* pmt; + mpeg_ts_enc_context_t* tsctx; + + if (pn < 1 || bytes < 0 || bytes >= (1 << 12)) + return -1; // EINVAL: pminfo-len 12-bits + + tsctx = (mpeg_ts_enc_context_t*)ts; + for (i = 0; i < tsctx->pat.pmt_count; i++) + { + pmt = &tsctx->pat.pmts[i]; + if (pmt->pn == pn) + return -1; // EEXIST + } + + assert(tsctx->pat.pmt_count == i); + pmt = pat_alloc_pmt(&tsctx->pat); + if (!pmt) + return -1; // E2BIG + + pmt->pid = tsctx->pid++; + pmt->pn = pn; + pmt->ver = 0x00; + pmt->cc = 0; + pmt->PCR_PID = 0x1FFF; // 0x1FFF-don't set PCR + + if (bytes > 0 && info) + { + pmt->pminfo = (uint8_t*)malloc(bytes); + if (!pmt->pminfo) + return -1; // ENOMEM + memcpy(pmt->pminfo, info, bytes); + pmt->pminfo_len = bytes; + } + + tsctx->pat.pmt_count++; + mpeg_ts_reset(ts); // update PAT/PMT + return 0; +} + +int mpeg_ts_remove_program(void* ts, uint16_t pn) +{ + unsigned int i; + struct pmt_t* pmt = NULL; + mpeg_ts_enc_context_t* tsctx; + + tsctx = (mpeg_ts_enc_context_t*)ts; + for (i = 0; i < tsctx->pat.pmt_count; i++) + { + pmt = &tsctx->pat.pmts[i]; + if (pmt->pn != pn) + continue; + + pmt_clear(pmt); + if (i + 1 < tsctx->pat.pmt_count) + memmove(&tsctx->pat.pmts[i], &tsctx->pat.pmts[i + 1], (tsctx->pat.pmt_count - i - 1) * sizeof(tsctx->pat.pmts[0])); + tsctx->pat.pmt_count--; + mpeg_ts_reset(ts); // update PAT/PMT + return 0; + } + + return -1; // ENOTFOUND +} + +static int mpeg_ts_pmt_add_stream(mpeg_ts_enc_context_t* ts, struct pmt_t* pmt, int codecid, const void* extra_data, size_t extra_data_size) +{ + struct pes_t* stream = NULL; + if (!ts || !pmt || pmt->stream_count >= sizeof(pmt->streams) / sizeof(pmt->streams[0])) + { + assert(0); + return -1; + } + + stream = &pmt->streams[pmt->stream_count]; + stream->codecid = (uint8_t)codecid; + stream->pid = (uint16_t)ts->pid++; + stream->esinfo_len = 0; + stream->esinfo = NULL; + + // stream id + // Table 2-22 - Stream_id assignments + if (mpeg_stream_type_video(codecid)) + { + // Rec. ITU-T H.262 | ISO/IEC 13818-2, ISO/IEC 11172-2, ISO/IEC 14496-2 + // or Rec. ITU-T H.264 | ISO/IEC 14496-10 video stream number + stream->sid = PES_SID_VIDEO; + } + else if (mpeg_stream_type_audio(codecid)) + { + // ISO/IEC 13818-3 or ISO/IEC 11172-3 or ISO/IEC 13818-7 or ISO/IEC 14496-3 + // audio stream number + stream->sid = PES_SID_AUDIO; + } + else + { + // private_stream_1 + stream->sid = PES_SID_PRIVATE_1; + } + + if (extra_data_size > 0 && extra_data) + { + stream->esinfo = malloc(extra_data_size); + if (!stream->esinfo) + return -ENOMEM; + memcpy(stream->esinfo, extra_data, extra_data_size); + stream->esinfo_len = (uint16_t)extra_data_size; + } + + pmt->stream_count++; + pmt->ver = (pmt->ver + 1) % 32; + mpeg_ts_reset(ts); // immediate update pat/pmt + return stream->pid; +} + +int mpeg_ts_add_stream(void* ts, int codecid, const void* extra_data, size_t extra_data_size) +{ + struct pmt_t *pmt = NULL; + mpeg_ts_enc_context_t *tsctx; + + tsctx = (mpeg_ts_enc_context_t*)ts; + if (0 == tsctx->pat.pmt_count) + { + // add default program + if (0 != mpeg_ts_add_program(tsctx, 1, NULL, 0)) + return -1; + } + pmt = &tsctx->pat.pmts[0]; + + return mpeg_ts_pmt_add_stream(tsctx, pmt, codecid, extra_data, extra_data_size); +} + +int mpeg_ts_add_program_stream(void* ts, uint16_t pn, int codecid, const void* extra_data, size_t extra_data_size) +{ + unsigned int i; + struct pmt_t* pmt = NULL; + mpeg_ts_enc_context_t* tsctx; + + tsctx = (mpeg_ts_enc_context_t*)ts; + for (i = 0; i < tsctx->pat.pmt_count; i++) + { + pmt = &tsctx->pat.pmts[i]; + if (pmt->pn == pn) + return mpeg_ts_pmt_add_stream(tsctx, pmt, codecid, extra_data, extra_data_size); + } + + return -1; // ENOTFOUND: program not found +} diff --git a/MediaServer/libmpeg/source/mpeg-ts-h264.c b/MediaServer/libmpeg/source/mpeg-ts-h264.c new file mode 100644 index 0000000..bc6c62b --- /dev/null +++ b/MediaServer/libmpeg/source/mpeg-ts-h264.c @@ -0,0 +1,223 @@ +#include "mpeg-types.h" +#include "mpeg-util.h" +#include "mpeg-proto.h" +#include +#include + +#define H264_NAL_IDR 5 +#define H264_NAL_AUD 9 + +/// e.g. +/// 1. 0x00 00 00 00 00 01 09 EF => return 6(09), leading 4 +/// 2. 0x80 00 00 00 00 01 09 EF => return 6(09), leading 4 +/// +/// Find h264 nalu start position +/// @param[out] leading leading bytes before nalu position +/// @return -1-not found, other nalu position(after 00 00 01) +int mpeg_h264_find_nalu(const uint8_t* p, size_t bytes, size_t* leading) +{ + size_t i, zeros; + for (zeros = i = 0; i + 2 /*naltype + 1-data*/ < bytes; i++) + { + if (0x01 == p[i] && zeros >= 2) + { + assert(i >= zeros); + if (leading) + *leading = (zeros > 2 ? 3 : zeros) + 1; // zeros + 0x01 + return (int)(i + 1); + } + + zeros = 0x00 != p[i] ? 0 : (zeros + 1); + } + + return -1; +} + +/// @param[out] leading optional leading zero bytes +/// @return -1-not found, other-AUD position(include start code) +static int mpeg_h264_find_access_unit_delimiter(const uint8_t* p, size_t bytes, size_t* leading) +{ + int i; + size_t off; + for (off = i = 0; off < bytes; off += i) // 00 00 00 01 00 ? + { + i = mpeg_h264_find_nalu(p + off, bytes - off, leading); + if (-1 == i) + return -1; + + if (H264_NAL_AUD == (p[i + off] & 0x1f)) + return (int)(i + off); + } + + return -1; +} + +/// @return 0-not find, 1-find ok +int mpeg_h264_start_with_access_unit_delimiter(const uint8_t* p, size_t bytes) +{ + int i; + size_t off; + uint8_t nalu; + for (off = i = 0; off < bytes; off += i) + { + i = mpeg_h264_find_nalu(p + off, bytes - off, NULL); + if (-1 == i) + return 0; + + assert(i > 0); + nalu = p[i + off] & 0x1f; + if (0 == nalu) + continue; // 00 00 00 01 00 ? + return H264_NAL_AUD == nalu ? 1 : 0; + } + + return 0; +} + +int mpeg_h264_find_keyframe(const uint8_t* p, size_t bytes) +{ + size_t i; + uint8_t type; + for (i = 2; i + 1 < bytes; i++) + { + if (0x01 == p[i] && 0x00 == p[i - 1] && 0x00 == p[i - 2]) + { + type = p[i + 1] & 0x1f; + if (H264_NAL_IDR >= type && 1 <= type) + return H264_NAL_IDR == type ? 1 : 0; + } + } + + return 0; +} + +/// h264_is_new_access_unit H.264 new access unit(frame) +/// @return 1-new access, 0-not a new access +static int mpeg_h264_is_new_access_unit(const uint8_t* nalu, size_t bytes) +{ + enum { NAL_NIDR = 1, NAL_PARTITION_A = 2, NAL_IDR = 5, NAL_SEI = 6, NAL_SPS = 7, NAL_PPS = 8, NAL_AUD = 9, }; + + uint8_t nal_type; + + if(bytes < 2) + return 0; + + nal_type = nalu[0] & 0x1f; + + // 7.4.1.2.3 Order of NAL units and coded pictures and association to access units + if(NAL_AUD == nal_type || NAL_SPS == nal_type || NAL_PPS == nal_type || NAL_SEI == nal_type || (14 <= nal_type && nal_type <= 18)) + return 1; + + // 7.4.1.2.4 Detection of the first VCL NAL unit of a primary coded picture + if(NAL_NIDR == nal_type || NAL_PARTITION_A == nal_type || NAL_IDR == nal_type) + { + // Live555 H264or5VideoStreamParser::parse + // The high-order bit of the byte after the "nal_unit_header" tells us whether it's + // the start of a new 'access unit' (and thus the current NAL unit ends an 'access unit'): + return (nalu[1] & 0x80) ? 1 : 0; // first_mb_in_slice + } + + return 0; +} + +int mpeg_h264_find_new_access_unit(const uint8_t* data, size_t bytes, int* vcl) +{ + int n; + size_t leading; + uint8_t nal_type; + const uint8_t* p, *end; + + end = data + bytes; + for (p = data; p && p < end; p += n) + { + n = mpeg_h264_find_nalu(p, end - p, &leading); + if (n < 0) + return -1; + + nal_type = p[n] & 0x1f; + if (*vcl > 0 && mpeg_h264_is_new_access_unit(p + n, end - p - n)) + { + return (int)(p - data + n - leading); + } + else if (nal_type > 0 && nal_type < 6) + { + *vcl = H264_NAL_IDR == nal_type ? MPEG_VCL_IDR : MPEG_VCL_P; + } + else if (PES_SID_START == p[n]) + { + // pes data loss ??? + *vcl = MPEG_VCL_CORRUPT; + return (int)(p - data + n - leading); + } + else + { + // nothing to do + } + } + + return -1; +} + +/// @param[out] codec 1-h264, 2-h265 +/// @return 0-ok, other-error +int mpeg_h26x_verify(const uint8_t* data, size_t bytes, int* codec) +{ + uint32_t h264_flags = 0x01A0U; // sps/pps/idr + uint64_t h265_flags = 0x700000000ULL; // vps/sps/pps + uint32_t h266_flags = 0xC000U; // sps/pps + + int n, count; + size_t leading; + uint8_t h26x[5][10]; + const uint8_t* p, * end; + + count = 0; + end = data + bytes; + for (p = data; p && p < end && count < sizeof(h26x[0])/sizeof(h26x[0][0]); p += n) + { + n = mpeg_h264_find_nalu(p, end - p, &leading); + if (n < 0 || p + n + 1 > end) + break; + + h26x[0][count] = p[n] & 0x1f; // h264 + h26x[1][count] = (p[n] >> 1) & 0x3f; // h265 + h26x[2][count] = (p[n+1] >> 3) & 0x1f; // h266 + h26x[3][count] = p[n]; // for mpeg4 vop_start_code + h26x[4][count] = p[n+1]; // for mpeg4 vop_coding_type + ++count; + } + + for(n = 0; n < count; n++) + { + h264_flags &= ~(1U << h26x[0][n]); + h265_flags &= ~(1ULL << h26x[1][n]); + h266_flags &= ~(1ULL << h26x[2][n]); + } + + if (0 == h264_flags && 0 != h265_flags && 0 != h266_flags) + { + // match sps/pps/idr + *codec = 1; + return 0; + } + else if (0 == h265_flags && 0 != h264_flags && 0 != h266_flags) + { + // match vps/sps/pps + *codec = 2; + return 0; + } + else if (0 == h266_flags && 0 != h264_flags && 0 != h265_flags) + { + // match sps/pps + *codec = 3; + return 0; + } + else if (0xB0 == h26x[3][0] && 0 == (0x30 & h26x[4][0])) + { + // match VOP start code + *codec = 4; + return 0; + } + + return -1; +} diff --git a/MediaServer/libmpeg/source/mpeg-ts-h265.c b/MediaServer/libmpeg/source/mpeg-ts-h265.c new file mode 100644 index 0000000..a6666a9 --- /dev/null +++ b/MediaServer/libmpeg/source/mpeg-ts-h265.c @@ -0,0 +1,134 @@ +#include "mpeg-types.h" +#include "mpeg-util.h" +#include "mpeg-proto.h" +#include +#include + +#define H265_NAL_BLA_W_LP 16 +#define H265_NAL_RSV_IRAP 23 +#define H265_NAL_VPS 32 +#define H265_NAL_SPS 33 +#define H265_NAL_PPS 34 +#define H265_NAL_AUD 35 +#define H265_NAL_SEI_PREFIX 39 +#define H265_NAL_SEI_SUFFIX 40 + +/// @param[out] leading optional leading zero bytes +/// @return -1-not found, other-AUD position(include start code) +static int mpeg_h265_find_access_unit_delimiter(const uint8_t* p, size_t bytes, size_t* leading) +{ + size_t i, zeros; + for (zeros = i = 0; i + 1 < bytes; i++) + { + if (0x01 == p[i] && zeros >= 2 && H265_NAL_AUD == ((p[i + 1] >> 1) & 0x3f)) + { + assert(i >= zeros); + if (leading) + *leading = (zeros > 2 ? 3 : 2) + 1; // zeros - (zeros > 2 ? 3 : 2); + return (int)(i - (zeros > 2 ? 3 : 2)); + } + + zeros = 0x00 != p[i] ? 0 : (zeros + 1); + } + + return -1; +} + +/// @return 0-not find, 1-find ok +int mpeg_h265_start_with_access_unit_delimiter(const uint8_t* p, size_t bytes) +{ + int i; + uint8_t nalu; + i = mpeg_h264_find_nalu(p, bytes, NULL); + if (-1 == i) + return 0; + + assert(i > 0); + nalu = (p[i] >> 1) & 0x3f; + return H265_NAL_AUD == nalu ? 1 : 0; +} + +// Rec. ITU-T H.265 v4 (12/2016) (p26) +// intra random access point (IRAP) picture: +// A coded picture for which each VCL NAL unit has nal_unit_type +// in the range of BLA_W_LP to RSV_IRAP_VCL23, inclusive. +static int mpeg_h265_find_keyframe(const uint8_t* p, size_t bytes) +{ + size_t i; + uint8_t type; + for (i = 2; i + 1 < bytes; i++) + { + if (0x01 == p[i] && 0x00 == p[i - 1] && 0x00 == p[i - 2]) + { + type = (p[i + 1] >> 1) & 0x3f; + if (type < 32) + return (16 <= type && type <= 23) ? 1 : 0; + } + } + + return 0; +} + +static int mpeg_h265_is_new_access_unit(const uint8_t* nalu, size_t bytes) +{ + uint8_t nal_type; + uint8_t nuh_layer_id; + + if(bytes < 3) + return 0; + + nal_type = (nalu[0] >> 1) & 0x3f; + nuh_layer_id = ((nalu[0] & 0x01) << 5) | ((nalu[1] >> 3) &0x1F); + + // 7.4.2.4.4 Order of NAL units and coded pictures and their association to access units + if(H265_NAL_VPS == nal_type || H265_NAL_SPS == nal_type || H265_NAL_PPS == nal_type || + (nuh_layer_id == 0 && (H265_NAL_AUD == nal_type || H265_NAL_SEI_PREFIX == nal_type || (41 <= nal_type && nal_type <= 44) || (48 <= nal_type && nal_type <= 55)))) + return 1; + + // 7.4.2.4.5 Order of VCL NAL units and association to coded pictures + if (nal_type < H265_NAL_VPS) + { + //first_slice_segment_in_pic_flag 0x80 + return (nalu[2] & 0x80) ? 1 : 0; + } + + return 0; +} + +int mpeg_h265_find_new_access_unit(const uint8_t* data, size_t bytes, int* vcl) +{ + int n; + size_t leading; + uint8_t nal_type; + const uint8_t* p, *end; + + end = data + bytes; + for (p = data; p && p < end; p += n) + { + n = mpeg_h264_find_nalu(p, end - p, &leading); + if (n < 0) + return -1; + + nal_type = (p[n] >> 1) & 0x3f; + if (*vcl > 0 && mpeg_h265_is_new_access_unit(p+n, end - p - n)) + { + return (int)(p - data + n - leading); + } + else if (nal_type < H265_NAL_VPS) + { + *vcl = (H265_NAL_BLA_W_LP <= nal_type && nal_type <= H265_NAL_RSV_IRAP) ? MPEG_VCL_IDR : MPEG_VCL_P; + } + else if (PES_SID_START == p[n]) + { + // pes data loss ??? + *vcl = MPEG_VCL_CORRUPT; // for assert + return (int)(p - data + n - leading); + } + else + { + // nothing to do + } + } + + return -1; +} diff --git a/MediaServer/libmpeg/source/mpeg-ts-h266.c b/MediaServer/libmpeg/source/mpeg-ts-h266.c new file mode 100644 index 0000000..d558d4d --- /dev/null +++ b/MediaServer/libmpeg/source/mpeg-ts-h266.c @@ -0,0 +1,98 @@ +#include "mpeg-types.h" +#include "mpeg-util.h" +#include "mpeg-proto.h" +#include +#include + +#define H266_NAL_IDR_W_RADL 7 +#define H266_NAL_RSV_IRAP 11 +#define H266_NAL_OPI 12 +#define H266_NAL_DCI 13 +#define H266_NAL_VPS 14 +#define H266_NAL_SPS 15 +#define H266_NAL_PPS 16 +#define H266_PREFIX_APS_NUT 17 +#define H266_SUFFIX_APS_NUT 18 +#define H266_PH_NUT 19 +#define H266_NAL_AUD 20 +#define H266_NAL_PREFIX_SEI 23 +#define H266_NAL_SUFFIX_SEI 24 + +/// @return 0-not find, 1-find ok +int mpeg_h266_start_with_access_unit_delimiter(const uint8_t* p, size_t bytes) +{ + int i; + uint8_t nalu; + i = mpeg_h264_find_nalu(p, bytes, NULL); + if (-1 == i) + return 0; + + assert(i > 0); + nalu = (p[i + 1] >> 3) & 0x1f; + return H266_NAL_AUD == nalu ? 1 : 0; +} + +static int mpeg_h266_is_new_access_unit(const uint8_t* nalu, size_t bytes) +{ + uint8_t nal_type; + uint8_t nuh_layer_id; + + if (bytes < 3) + return 0; + + nal_type = (nalu[1] >> 3) & 0x1f; + nuh_layer_id = nalu[0] & 0x3F; + + // 7.4.2.4.3 Order of PUs and their association to AUs + if (H266_NAL_AUD == nal_type || H266_NAL_OPI == nal_type || H266_NAL_DCI == nal_type || H266_NAL_VPS == nal_type || H266_NAL_SPS == nal_type || H266_NAL_PPS == nal_type || + (nuh_layer_id == 0 && (H266_PREFIX_APS_NUT == nal_type || H266_PH_NUT == nal_type || H266_NAL_PREFIX_SEI == nal_type || + 26 == nal_type || (28 <= nal_type && nal_type <= 29)))) + return 1; + + // 7.4.2.4.4 Order of NAL units and coded pictures and their association to PUs + if (nal_type < H266_NAL_OPI) + { + //sh_picture_header_in_slice_header_flag == 1 + return (nalu[2] & 0x80) ? 1 : 0; + } + + return 0; +} + +int mpeg_h266_find_new_access_unit(const uint8_t* data, size_t bytes, int* vcl) +{ + int n; + size_t leading; + uint8_t nal_type; + const uint8_t* p, * end; + + end = data + bytes; + for (p = data; p && p < end; p += n) + { + n = mpeg_h264_find_nalu(p, end - p, &leading); + if (n < 1) + return -1; + + nal_type = (p[n+1] >> 3) & 0x1f; + if (*vcl > 0 && mpeg_h266_is_new_access_unit(p + n, end - p - n)) + { + return (int)(p - data + n - leading); + } + else if (nal_type < H266_NAL_OPI) + { + *vcl = (H266_NAL_IDR_W_RADL <= nal_type && nal_type <= H266_NAL_RSV_IRAP) ? MPEG_VCL_IDR : MPEG_VCL_P; + } + else if (PES_SID_START == p[n]) + { + // pes data loss ??? + *vcl = MPEG_VCL_CORRUPT; // for assert + return (int)(p - data + n - leading); + } + else + { + // nothing to do + } + } + + return -1; +} diff --git a/MediaServer/libmpeg/source/mpeg-ts-internal.h b/MediaServer/libmpeg/source/mpeg-ts-internal.h new file mode 100644 index 0000000..6f71fc5 --- /dev/null +++ b/MediaServer/libmpeg/source/mpeg-ts-internal.h @@ -0,0 +1,103 @@ +#ifndef _mpeg_ts_internal_h_ +#define _mpeg_ts_internal_h_ + +#include "mpeg-proto.h" +#include "mpeg-types.h" +#include "mpeg-pes-internal.h" +#include "mpeg-util.h" + +#define TS_PACKET_SIZE 188 + +#define TS_SYNC_BYTE 0x47 + +struct ts_adaptation_field_t +{ + unsigned int adaptation_field_length : 8; + unsigned int discontinuity_indicator : 1; + unsigned int random_access_indicator : 1; + unsigned int elementary_stream_priority_indicator : 1; + unsigned int PCR_flag : 1; + unsigned int OPCR_flag : 1; + unsigned int splicing_point_flag : 1; + unsigned int transport_private_data_flag : 1; + unsigned int adaptation_field_extension_flag : 1; + + int64_t program_clock_reference_base; // 33-bits + unsigned int program_clock_reference_extension; // 9-bits + + int64_t original_program_clock_reference_base; // 33-bits + unsigned int original_program_clock_reference_extension; // 9-bits + + unsigned int splice_countdown : 8; + + unsigned int transport_private_data_length : 8; + + unsigned int adaptation_field_extension_length : 8; + unsigned int ltw_flag : 1; + unsigned int piecewise_rate_flag : 1; + unsigned int seamless_splice_flag : 1; + + unsigned int ltw_valid_flag : 1; + unsigned int ltw_offset : 15; + + unsigned int piecewise_rate : 22; + + unsigned int Splice_type : 4; + int64_t DTS_next_AU; +}; + +struct ts_packet_header_t +{ + unsigned int transport_error_indicator : 1; + unsigned int payload_unit_start_indicator : 1; + unsigned int transport_priority : 1; + + unsigned int transport_scrambling_control : 2; + unsigned int adaptation_field_control : 2; + unsigned int continuity_counter : 4; + + struct ts_adaptation_field_t adaptation; +}; + +struct pmt_t +{ + unsigned int pid; // PID : 13 [0x0010, 0x1FFE] + unsigned int pn; // program_number: 16 [1, 0xFFFF] + unsigned int ver; // version_number : 5 + unsigned int cc; // continuity_counter : 4 + unsigned int PCR_PID; // 13-bits + unsigned int pminfo_len;// program_info_length : 12 + uint8_t* pminfo; // program_info; + + char provider[64]; + char name[64]; + char proginfo[4]; // CUEI + + unsigned int stream_count; + struct pes_t streams[4]; +}; + +struct pat_t +{ + unsigned int tsid; // transport_stream_id : 16; + unsigned int ver; // version_number : 5; + unsigned int cc; //continuity_counter : 4; + + unsigned int pmt_count; + unsigned int pmt_capacity; + struct pmt_t pmt_default[1]; + struct pmt_t* pmts; +}; + +struct pmt_t* pat_alloc_pmt(struct pat_t* pat); +struct pmt_t* pat_find(struct pat_t* pat, uint16_t pn); +size_t pat_read(struct pat_t *pat, const uint8_t* data, size_t bytes); +size_t pat_write(const struct pat_t *pat, uint8_t *data); +size_t pmt_read(struct pmt_t *pmt, const uint8_t* data, size_t bytes); +size_t pmt_write(const struct pmt_t *pmt, uint8_t *data); +size_t sdt_read(struct pat_t *pat, const uint8_t* data, size_t bytes); +size_t sdt_write(const struct pat_t* pat, uint8_t* data); +void pat_clear(struct pat_t* pat); +void pmt_clear(struct pmt_t* pmt); + +#endif /* !_mpeg_ts_internal_h_ */ diff --git a/MediaServer/libmpeg/source/mpeg-ts-opus.h b/MediaServer/libmpeg/source/mpeg-ts-opus.h new file mode 100644 index 0000000..5d8e989 --- /dev/null +++ b/MediaServer/libmpeg/source/mpeg-ts-opus.h @@ -0,0 +1,63 @@ +#ifndef _mpeg_ts_opus_h_ +#define _mpeg_ts_opus_h_ + +#include + +// https://wiki.xiph.org/OpusTS +// https://people.xiph.org/~tterribe/opus/ETSI_TS_opus-v0.1.3-draft.doc (death link) +// https://patchwork.ffmpeg.org/project/ffmpeg/patch/5e3b1495-22de-346f-ab85-f022739c73a3@gmail.com/ +/* + * Table 4-2 opus_audio_descriptor syntax + + |Syntax |Number of bits |Identif| + | | |ier | + |opus_audio_descriptor() { | | | + | descriptor_tag |8 |uimsbf | + | descriptor_length |8 |uimsbf | + | channel_config_code |8 |uimsbf | + | if(channel_config_code==0x81) { | | | + | channel_count |8 |uimsbf | + | mapping_family |8 |uimsbf | + | if(mapping_family>0) { | | | + | stream_count_minus_one |ceil(log2(channel_count)) |uimsbf | + | coupled_stream_count |ceil(log2(stream_count+1))|uimsbf | + | for(i=0; i> 25) & 0xFF; + ptr[1] = (pcr_base >> 17) & 0xFF; + ptr[2] = (pcr_base >> 9) & 0xFF; + ptr[3] = (pcr_base >> 1) & 0xFF; + ptr[4] = ((pcr_base & 0x01) << 7) | 0x7E | ((pcr_ext>>8) & 0x01); + ptr[5] = pcr_ext & 0xFF; +} + +int mpeg_stream_type_video(int codecid) +{ + switch (codecid) + { + case PSI_STREAM_H264: + case PSI_STREAM_H265: + case PSI_STREAM_H266: + case PSI_STREAM_MPEG1: + case PSI_STREAM_MPEG2: + case PSI_STREAM_MPEG4: + case PSI_STREAM_VIDEO_VC1: + case PSI_STREAM_VIDEO_SVAC: + case PSI_STREAM_VIDEO_DIRAC: + case PSI_STREAM_VIDEO_CAVS: + case PSI_STREAM_VIDEO_AVS3: + case PSI_STREAM_VP8: + case PSI_STREAM_VP9: + case PSI_STREAM_AV1: + return 1; + default: + return 0; + } +} + +int mpeg_stream_type_audio(int codecid) +{ + switch (codecid) + { + case PSI_STREAM_AAC: + case PSI_STREAM_MPEG4_AAC: + case PSI_STREAM_MPEG4_AAC_LATM: + case PSI_STREAM_AUDIO_MPEG1: + case PSI_STREAM_MP3: + case PSI_STREAM_AUDIO_AC3: + case PSI_STREAM_AUDIO_DTS: + case PSI_STREAM_AUDIO_EAC3: + case PSI_STREAM_AUDIO_SVAC: + case PSI_STREAM_AUDIO_G711A: + case PSI_STREAM_AUDIO_G711U: + case PSI_STREAM_AUDIO_G722: + case PSI_STREAM_AUDIO_G723: + case PSI_STREAM_AUDIO_G729: + case PSI_STREAM_AUDIO_OPUS: + return 1; + default: + return 0; + } +} diff --git a/Server/UdpServer.h b/Server/UdpServer.h index f36b155..33690d8 100644 --- a/Server/UdpServer.h +++ b/Server/UdpServer.h @@ -1,7 +1,7 @@ #ifndef UDPSERVER_H #define UDPSERVER_H -#include "boost/asio.hpp" +#include class UdpServer { public: