add codec.
This commit is contained in:
parent
9de3af15eb
commit
08340ad5c8
@ -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
|
||||
)
|
69
MediaServer/libflv/include/amf0.h
Normal file
69
MediaServer/libflv/include/amf0.h
Normal file
@ -0,0 +1,69 @@
|
||||
#ifndef _amf0_h_
|
||||
#define _amf0_h_
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stddef.h>
|
||||
|
||||
#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_ */
|
56
MediaServer/libflv/include/amf3.h
Normal file
56
MediaServer/libflv/include/amf3.h
Normal file
@ -0,0 +1,56 @@
|
||||
#ifndef _amf3_h_
|
||||
#define _amf3_h_
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stddef.h>
|
||||
|
||||
#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_ */
|
54
MediaServer/libflv/include/aom-av1.h
Normal file
54
MediaServer/libflv/include/aom-av1.h
Normal file
@ -0,0 +1,54 @@
|
||||
#ifndef _aom_av1_h_
|
||||
#define _aom_av1_h_
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stddef.h>
|
||||
|
||||
#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_ */
|
37
MediaServer/libflv/include/avswg-avs3.h
Normal file
37
MediaServer/libflv/include/avswg-avs3.h
Normal file
@ -0,0 +1,37 @@
|
||||
#ifndef _avswg_avs3_h_
|
||||
#define _avswg_avs3_h_
|
||||
|
||||
// http://standard.avswg.org.cn/avs3_download/
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stddef.h>
|
||||
|
||||
#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_ */
|
38
MediaServer/libflv/include/flv-demuxer.h
Normal file
38
MediaServer/libflv/include/flv-demuxer.h
Normal file
@ -0,0 +1,38 @@
|
||||
#ifndef _flv_demuxer_h_
|
||||
#define _flv_demuxer_h_
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stddef.h>
|
||||
|
||||
#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_ */
|
118
MediaServer/libflv/include/flv-header.h
Normal file
118
MediaServer/libflv/include/flv-header.h
Normal file
@ -0,0 +1,118 @@
|
||||
#ifndef _flv_header_h_
|
||||
#define _flv_header_h_
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stddef.h>
|
||||
|
||||
#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_ */
|
73
MediaServer/libflv/include/flv-muxer.h
Normal file
73
MediaServer/libflv/include/flv-muxer.h
Normal file
@ -0,0 +1,73 @@
|
||||
#ifndef _flv_muxer_h_
|
||||
#define _flv_muxer_h_
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#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_ */
|
53
MediaServer/libflv/include/flv-parser.h
Normal file
53
MediaServer/libflv/include/flv-parser.h
Normal file
@ -0,0 +1,53 @@
|
||||
#ifndef _flv_parser_h_
|
||||
#define _flv_parser_h_
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stddef.h>
|
||||
#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_ */
|
105
MediaServer/libflv/include/flv-proto.h
Normal file
105
MediaServer/libflv/include/flv-proto.h
Normal file
@ -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_ */
|
26
MediaServer/libflv/include/flv-reader.h
Normal file
26
MediaServer/libflv/include/flv-reader.h
Normal file
@ -0,0 +1,26 @@
|
||||
#ifndef _flv_reader_h_
|
||||
#define _flv_reader_h_
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stddef.h>
|
||||
|
||||
#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_ */
|
41
MediaServer/libflv/include/flv-writer.h
Normal file
41
MediaServer/libflv/include/flv-writer.h
Normal file
@ -0,0 +1,41 @@
|
||||
#ifndef _flv_writer_h_
|
||||
#define _flv_writer_h_
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#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_ */
|
109
MediaServer/libflv/include/mp3-header.h
Normal file
109
MediaServer/libflv/include/mp3-header.h
Normal file
@ -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_ */
|
156
MediaServer/libflv/include/mpeg4-aac.h
Normal file
156
MediaServer/libflv/include/mpeg4-aac.h
Normal file
@ -0,0 +1,156 @@
|
||||
#ifndef _mpeg4_aac_h_
|
||||
#define _mpeg4_aac_h_
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#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_ */
|
78
MediaServer/libflv/include/mpeg4-avc.h
Normal file
78
MediaServer/libflv/include/mpeg4-avc.h
Normal file
@ -0,0 +1,78 @@
|
||||
#ifndef _mpeg4_avc_h_
|
||||
#define _mpeg4_avc_h_
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stddef.h>
|
||||
|
||||
#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_ */
|
201
MediaServer/libflv/include/mpeg4-bits.h
Normal file
201
MediaServer/libflv/include/mpeg4-bits.h
Normal file
@ -0,0 +1,201 @@
|
||||
#ifndef _mpeg4_bits_h_
|
||||
#define _mpeg4_bits_h_
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stddef.h>
|
||||
#include <assert.h>
|
||||
|
||||
#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_ */
|
68
MediaServer/libflv/include/mpeg4-hevc.h
Normal file
68
MediaServer/libflv/include/mpeg4-hevc.h
Normal file
@ -0,0 +1,68 @@
|
||||
#ifndef _mpeg4_hevc_h_
|
||||
#define _mpeg4_hevc_h_
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stddef.h>
|
||||
|
||||
#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_ */
|
73
MediaServer/libflv/include/mpeg4-vvc.h
Normal file
73
MediaServer/libflv/include/mpeg4-vvc.h
Normal file
@ -0,0 +1,73 @@
|
||||
#ifndef _mpeg_vvc_h
|
||||
#define _mpeg_vvc_h
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stddef.h>
|
||||
|
||||
#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 */
|
39
MediaServer/libflv/include/opus-head.h
Normal file
39
MediaServer/libflv/include/opus-head.h
Normal file
@ -0,0 +1,39 @@
|
||||
#ifndef _opus_head_h_
|
||||
#define _opus_head_h_
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#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_ */
|
43
MediaServer/libflv/include/riff-acm.h
Normal file
43
MediaServer/libflv/include/riff-acm.h
Normal file
@ -0,0 +1,43 @@
|
||||
#ifndef _riff_acm_h_
|
||||
#define _riff_acm_h_
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stddef.h>
|
||||
|
||||
#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_ */
|
35
MediaServer/libflv/include/webm-vpx.h
Normal file
35
MediaServer/libflv/include/webm-vpx.h
Normal file
@ -0,0 +1,35 @@
|
||||
#ifndef _webm_vpx_h_
|
||||
#define _webm_vpx_h_
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stddef.h>
|
||||
|
||||
#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_ */
|
591
MediaServer/libflv/source/amf0.c
Normal file
591
MediaServer/libflv/source/amf0.c
Normal file
@ -0,0 +1,591 @@
|
||||
#include "amf0.h"
|
||||
#include <stdint.h>
|
||||
#include <stddef.h>
|
||||
#include <string.h>
|
||||
#include <assert.h>
|
||||
|
||||
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
|
95
MediaServer/libflv/source/amf3.c
Normal file
95
MediaServer/libflv/source/amf3.c
Normal file
@ -0,0 +1,95 @@
|
||||
#include "amf3.h"
|
||||
#include <string.h>
|
||||
#include <assert.h>
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
607
MediaServer/libflv/source/aom-av1.c
Normal file
607
MediaServer/libflv/source/aom-av1.c
Normal file
@ -0,0 +1,607 @@
|
||||
#include "aom-av1.h"
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <assert.h>
|
||||
#include <stdio.h>
|
||||
#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
|
||||
// <sample entry 4CC>.<profile>.<level><tier>.<bitDepth>.<monochrome>.<chromaSubsampling>.<colorPrimaries>.<transferCharacteristics>.<matrixCoefficients>.<videoFullRangeFlag>
|
||||
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
|
90
MediaServer/libflv/source/avswg-avs3.c
Normal file
90
MediaServer/libflv/source/avswg-avs3.c
Normal file
@ -0,0 +1,90 @@
|
||||
#include "avswg-avs3.h"
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <assert.h>
|
||||
|
||||
#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
|
84
MediaServer/libflv/source/flv-demuxer-script.c
Normal file
84
MediaServer/libflv/source/flv-demuxer-script.c
Normal file
@ -0,0 +1,84 @@
|
||||
#include "flv-demuxer.h"
|
||||
#include "amf0.h"
|
||||
#include <errno.h>
|
||||
#include <assert.h>
|
||||
#include <string.h>
|
||||
|
||||
#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;
|
||||
}
|
347
MediaServer/libflv/source/flv-demuxer.c
Normal file
347
MediaServer/libflv/source/flv-demuxer.c
Normal file
@ -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 <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <assert.h>
|
||||
#include <errno.h>
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
333
MediaServer/libflv/source/flv-header.c
Normal file
333
MediaServer/libflv/source/flv-header.c
Normal file
@ -0,0 +1,333 @@
|
||||
#include "flv-header.h"
|
||||
#include "flv-proto.h"
|
||||
#include <assert.h>
|
||||
#include <errno.h>
|
||||
|
||||
#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;
|
||||
}
|
567
MediaServer/libflv/source/flv-muxer.c
Normal file
567
MediaServer/libflv/source/flv-muxer.c
Normal file
@ -0,0 +1,567 @@
|
||||
#include "flv-muxer.h"
|
||||
#include "flv-proto.h"
|
||||
#include "flv-header.h"
|
||||
#include "amf0.h"
|
||||
#include <stdio.h>
|
||||
#include <errno.h>
|
||||
#include <stdlib.h>
|
||||
#include <assert.h>
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
#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);
|
||||
}
|
261
MediaServer/libflv/source/flv-parser.c
Normal file
261
MediaServer/libflv/source/flv-parser.c
Normal file
@ -0,0 +1,261 @@
|
||||
#include "flv-parser.h"
|
||||
#include "flv-header.h"
|
||||
#include "flv-proto.h"
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <assert.h>
|
||||
#include <errno.h>
|
||||
|
||||
#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;
|
||||
}
|
135
MediaServer/libflv/source/flv-reader.c
Normal file
135
MediaServer/libflv/source/flv-reader.c
Normal file
@ -0,0 +1,135 @@
|
||||
#include "flv-reader.h"
|
||||
#include "flv-header.h"
|
||||
#include "flv-proto.h"
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <assert.h>
|
||||
#include <stdio.h>
|
||||
|
||||
#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;
|
||||
}
|
162
MediaServer/libflv/source/flv-writer.c
Normal file
162
MediaServer/libflv/source/flv-writer.c
Normal file
@ -0,0 +1,162 @@
|
||||
#include "flv-writer.h"
|
||||
#include "flv-header.h"
|
||||
#include "flv-proto.h"
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <assert.h>
|
||||
#include <stdio.h>
|
||||
|
||||
#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);
|
||||
}
|
474
MediaServer/libflv/source/hevc-annexbtomp4.c
Normal file
474
MediaServer/libflv/source/hevc-annexbtomp4.c
Normal file
@ -0,0 +1,474 @@
|
||||
#include "mpeg4-hevc.h"
|
||||
#include "mpeg4-avc.h"
|
||||
#include <string.h>
|
||||
#include <assert.h>
|
||||
#include <errno.h>
|
||||
|
||||
#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
|
134
MediaServer/libflv/source/hevc-mp4toannexb.c
Normal file
134
MediaServer/libflv/source/hevc-mp4toannexb.c
Normal file
@ -0,0 +1,134 @@
|
||||
#include "mpeg4-hevc.h"
|
||||
#include "mpeg4-avc.h"
|
||||
#include <string.h>
|
||||
#include <assert.h>
|
||||
#include <stdint.h>
|
||||
#include <stddef.h>
|
||||
#include <errno.h>
|
||||
|
||||
#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;
|
||||
}
|
233
MediaServer/libflv/source/mp3-header.c
Normal file
233
MediaServer/libflv/source/mp3-header.c
Normal file
@ -0,0 +1,233 @@
|
||||
#include "mp3-header.h"
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
#include <assert.h>
|
||||
|
||||
// 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
|
664
MediaServer/libflv/source/mpeg4-aac-asc.c
Normal file
664
MediaServer/libflv/source/mpeg4-aac-asc.c
Normal file
@ -0,0 +1,664 @@
|
||||
#include "mpeg4-aac.h"
|
||||
#include "mpeg4-bits.h"
|
||||
#include <assert.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <errno.h>
|
||||
|
||||
// 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; i<numOttBoxes; i++) { Note 2
|
||||
OttConfig(i);
|
||||
}
|
||||
for (i=0; i<numTttBoxes; i++) { Note 2
|
||||
TttConfig(i);
|
||||
}
|
||||
if (bsTempShapeConfig == 2) {
|
||||
bsEnvQuantMode 1 uimsbf
|
||||
}
|
||||
if (bs3DaudioMode) {
|
||||
bs3DaudioHRTFset; 2 uimsbf
|
||||
if (bs3DaudioHRTFset==0) {
|
||||
ParamHRTFset();
|
||||
}
|
||||
}
|
||||
ByteAlign();
|
||||
SpatialExtensionConfig();
|
||||
}
|
||||
*/
|
||||
static int SpatialSpecificConfig(struct mpeg4_bits_t* bits, struct mpeg4_aac_t* aac)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
static inline uint8_t mpeg4_aac_get_audio_object_type(struct mpeg4_bits_t* bits)
|
||||
{
|
||||
uint8_t audioObjectType;
|
||||
audioObjectType = mpeg4_bits_read_uint8(bits, 5);
|
||||
if (31 == audioObjectType)
|
||||
audioObjectType = 32 + mpeg4_bits_read_uint8(bits, 6);
|
||||
return audioObjectType;
|
||||
}
|
||||
|
||||
static inline uint8_t mpeg4_aac_get_sampling_frequency(struct mpeg4_bits_t* bits)
|
||||
{
|
||||
uint8_t samplingFrequencyIndex;
|
||||
uint32_t samplingFrequency;
|
||||
samplingFrequencyIndex = mpeg4_bits_read_uint8(bits, 4);
|
||||
if (0x0F == samplingFrequencyIndex)
|
||||
samplingFrequency = mpeg4_bits_read_uint32(bits, 24);
|
||||
return samplingFrequencyIndex;
|
||||
}
|
||||
|
||||
/// @return asc bits
|
||||
static size_t mpeg4_aac_audio_specific_config_load3(struct mpeg4_bits_t* bits, struct mpeg4_aac_t* aac)
|
||||
{
|
||||
uint16_t syncExtensionType;
|
||||
// uint8_t audioObjectType;
|
||||
// uint8_t samplingFrequencyIndex = 0;
|
||||
uint8_t extensionSamplingFrequencyIndex = 0;
|
||||
// uint8_t channelConfiguration = 0;
|
||||
uint8_t epConfig;
|
||||
size_t offset;
|
||||
|
||||
offset = bits->bits;
|
||||
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);
|
||||
}
|
364
MediaServer/libflv/source/mpeg4-aac.c
Normal file
364
MediaServer/libflv/source/mpeg4-aac.c
Normal file
@ -0,0 +1,364 @@
|
||||
#include "mpeg4-aac.h"
|
||||
#include <assert.h>
|
||||
#include <string.h>
|
||||
#include <stdio.h>
|
||||
|
||||
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
|
518
MediaServer/libflv/source/mpeg4-annexbtomp4.c
Normal file
518
MediaServer/libflv/source/mpeg4-annexbtomp4.c
Normal file
@ -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 <string.h>
|
||||
#include <assert.h>
|
||||
#include <stdint.h>
|
||||
#include <stddef.h>
|
||||
#include <errno.h>
|
||||
|
||||
#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
|
277
MediaServer/libflv/source/mpeg4-avc.c
Normal file
277
MediaServer/libflv/source/mpeg4-avc.c
Normal file
@ -0,0 +1,277 @@
|
||||
#include "mpeg4-avc.h"
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <assert.h>
|
||||
|
||||
/*
|
||||
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
|
329
MediaServer/libflv/source/mpeg4-hevc.c
Normal file
329
MediaServer/libflv/source/mpeg4-hevc.c
Normal file
@ -0,0 +1,329 @@
|
||||
#include "mpeg4-hevc.h"
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <assert.h>
|
||||
|
||||
#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
|
170
MediaServer/libflv/source/mpeg4-mp4toannexb.c
Normal file
170
MediaServer/libflv/source/mpeg4-mp4toannexb.c
Normal file
@ -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 <string.h>
|
||||
#include <assert.h>
|
||||
#include <stdint.h>
|
||||
#include <stddef.h>
|
||||
#include <errno.h>
|
||||
|
||||
#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
|
427
MediaServer/libflv/source/mpeg4-vvc.c
Normal file
427
MediaServer/libflv/source/mpeg4-vvc.c
Normal file
@ -0,0 +1,427 @@
|
||||
#include "mpeg4-vvc.h"
|
||||
#include "mpeg4-bits.h"
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <assert.h>
|
||||
#include <errno.h>
|
||||
|
||||
#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
|
419
MediaServer/libflv/source/opus-head.c
Normal file
419
MediaServer/libflv/source/opus-head.c
Normal file
@ -0,0 +1,419 @@
|
||||
#include "opus-head.h"
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <assert.h>
|
||||
|
||||
// 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<stream_count-1; i++)| | |
|
||||
|{ | | |
|
||||
| self_delimited_opus_packet | | |
|
||||
| } | | |
|
||||
|undelimited_opus_packet | | |
|
||||
|} | | |
|
||||
*
|
||||
*
|
||||
* Table 6-2 opus_access_unit syntax
|
||||
|
||||
|Syntax |Number of |Identif|
|
||||
| |bits |ier |
|
||||
|opus_control_header() { | | |
|
||||
| control_header_prefix |11 |bslbf |
|
||||
| start_trim_flag |1 |bslbf |
|
||||
| end_trim_flag |1 |bslbf |
|
||||
| control_extension_flag |1 |bslbf |
|
||||
| Reserved |2 |bslbf |
|
||||
| au_size = 0 | | |
|
||||
|while(nextbits(8) == 0xFF){ | | |
|
||||
|ff_byte [= 0xFF] |8 |uimsbf |
|
||||
|au_size += 255; | | |
|
||||
|} | | |
|
||||
|au_size_last_byte |8 |uimsbf |
|
||||
|au_size += au_size_last_byte | | |
|
||||
|if(start_trim_flag==1) { | | |
|
||||
| Reserved |3 |bslbf |
|
||||
| start_trim |13 |uimsbf |
|
||||
| } | | |
|
||||
| if(end_trim_flag==1) { | | |
|
||||
| Reserved |3 |bslbf |
|
||||
| end_trim |13 |uimsbf |
|
||||
| } | | |
|
||||
| if(control_extension_flag==1) { | | |
|
||||
| control_extension_length |8 |uimsbf |
|
||||
| for(i=0; i<control_extension_length; i++)| | |
|
||||
|{ | | |
|
||||
| reserved |8 |bslbf |
|
||||
| } | | |
|
||||
| } | | |
|
||||
|} | | |
|
||||
*/
|
||||
|
||||
static const uint8_t* opus_ts_header(const uint8_t* data, size_t bytes, size_t* payload)
|
||||
{
|
||||
size_t i;
|
||||
int start_trim_flag;
|
||||
int end_trim_flag;
|
||||
int control_extension_flag;
|
||||
int au_size;
|
||||
uint16_t prefix;
|
||||
|
||||
if(bytes < 3)
|
||||
return NULL;
|
||||
|
||||
i = 0;
|
||||
prefix = ((uint16_t)data[0] << 8) | data[1];
|
||||
if(0x7FE0 == (prefix & 0xFFE0))
|
||||
{
|
||||
//opus control header
|
||||
start_trim_flag = (prefix >> 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
|
51
MediaServer/libflv/source/riff-acm.c
Normal file
51
MediaServer/libflv/source/riff-acm.c
Normal file
@ -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;
|
||||
}
|
433
MediaServer/libflv/source/vvc-annexbtomp4.c
Normal file
433
MediaServer/libflv/source/vvc-annexbtomp4.c
Normal file
@ -0,0 +1,433 @@
|
||||
#include "mpeg4-vvc.h"
|
||||
#include "mpeg4-avc.h"
|
||||
#include <string.h>
|
||||
#include <assert.h>
|
||||
#include <errno.h>
|
||||
|
||||
#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
|
138
MediaServer/libflv/source/vvc-mp4toannexb.c
Normal file
138
MediaServer/libflv/source/vvc-mp4toannexb.c
Normal file
@ -0,0 +1,138 @@
|
||||
#include "mpeg4-vvc.h"
|
||||
#include "mpeg4-avc.h"
|
||||
#include <string.h>
|
||||
#include <assert.h>
|
||||
#include <stdint.h>
|
||||
#include <stddef.h>
|
||||
#include <errno.h>
|
||||
|
||||
#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;
|
||||
}
|
171
MediaServer/libflv/source/webm-vpx.c
Normal file
171
MediaServer/libflv/source/webm-vpx.c
Normal file
@ -0,0 +1,171 @@
|
||||
#include "webm-vpx.h"
|
||||
#include "mpeg4-bits.h"
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <assert.h>
|
||||
|
||||
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
|
49
MediaServer/libmov/include/fmp4-writer.h
Normal file
49
MediaServer/libmov/include/fmp4-writer.h
Normal file
@ -0,0 +1,49 @@
|
||||
#ifndef _fmp4_writer_h_
|
||||
#define _fmp4_writer_h_
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
#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_ */
|
182
MediaServer/libmov/include/mov-atom.h
Normal file
182
MediaServer/libmov/include/mov-atom.h
Normal file
@ -0,0 +1,182 @@
|
||||
#ifndef _mov_atom_h_
|
||||
#define _mov_atom_h_
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stddef.h>
|
||||
|
||||
#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_ */
|
25
MediaServer/libmov/include/mov-box.h
Normal file
25
MediaServer/libmov/include/mov-box.h
Normal file
@ -0,0 +1,25 @@
|
||||
#ifndef _mov_box_h_
|
||||
#define _mov_box_h_
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stddef.h>
|
||||
|
||||
// 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_ */
|
33
MediaServer/libmov/include/mov-buffer.h
Normal file
33
MediaServer/libmov/include/mov-buffer.h
Normal file
@ -0,0 +1,33 @@
|
||||
#ifndef _mov_buffer_h_
|
||||
#define _mov_buffer_h_
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
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_ */
|
54
MediaServer/libmov/include/mov-format.h
Normal file
54
MediaServer/libmov/include/mov-format.h
Normal file
@ -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_ */
|
89
MediaServer/libmov/include/mov-memory-buffer.h
Normal file
89
MediaServer/libmov/include/mov-memory-buffer.h
Normal file
@ -0,0 +1,89 @@
|
||||
#ifndef _mov_memory_buffer_h_
|
||||
#define _mov_memory_buffer_h_
|
||||
|
||||
#include "mov-buffer.h"
|
||||
#include <stddef.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <stdint.h>
|
||||
#include <errno.h>
|
||||
|
||||
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_ */
|
54
MediaServer/libmov/include/mov-reader.h
Normal file
54
MediaServer/libmov/include/mov-reader.h
Normal file
@ -0,0 +1,54 @@
|
||||
#ifndef _mov_reader_h_
|
||||
#define _mov_reader_h_
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
#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_*/
|
53
MediaServer/libmov/include/mov-udta.h
Normal file
53
MediaServer/libmov/include/mov-udta.h
Normal file
@ -0,0 +1,53 @@
|
||||
#ifndef _mov_udta_h_
|
||||
#define _mov_udta_h_
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#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_ */
|
40
MediaServer/libmov/include/mov-writer.h
Normal file
40
MediaServer/libmov/include/mov-writer.h
Normal file
@ -0,0 +1,40 @@
|
||||
#ifndef _mov_writer_h_
|
||||
#define _mov_writer_h_
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
#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_ */
|
131
MediaServer/libmov/include/mp4-writer.h
Normal file
131
MediaServer/libmov/include/mp4-writer.h
Normal file
@ -0,0 +1,131 @@
|
||||
#ifndef _mp4_writer_h_
|
||||
#define _mp4_writer_h_
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
#include <assert.h>
|
||||
#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_ */
|
119
MediaServer/libmov/source/fmp4-reader.c
Normal file
119
MediaServer/libmov/source/fmp4-reader.c
Normal file
@ -0,0 +1,119 @@
|
||||
#include "mov-internal.h"
|
||||
#include <assert.h>
|
||||
#include <errno.h>
|
||||
|
||||
#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
|
||||
}
|
545
MediaServer/libmov/source/fmp4-writer.c
Normal file
545
MediaServer/libmov/source/fmp4-writer.c
Normal file
@ -0,0 +1,545 @@
|
||||
#include "fmp4-writer.h"
|
||||
#include "mov-internal.h"
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <assert.h>
|
||||
#include <errno.h>
|
||||
#include <time.h>
|
||||
|
||||
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<6C>\base<73>\is<69>\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<6C>\base<73>\is<69>\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);
|
||||
}
|
76
MediaServer/libmov/source/mov-avc1.c
Normal file
76
MediaServer/libmov/source/mov-avc1.c
Normal file
@ -0,0 +1,76 @@
|
||||
#include "mov-internal.h"
|
||||
#include <assert.h>
|
||||
#include <stdlib.h>
|
||||
#include <errno.h>
|
||||
|
||||
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;
|
||||
}
|
32
MediaServer/libmov/source/mov-dinf.c
Normal file
32
MediaServer/libmov/source/mov-dinf.c
Normal file
@ -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;
|
||||
}
|
136
MediaServer/libmov/source/mov-elst.c
Normal file
136
MediaServer/libmov/source/mov-elst.c
Normal file
@ -0,0 +1,136 @@
|
||||
#include "mov-internal.h"
|
||||
#include <errno.h>
|
||||
#include <stdlib.h>
|
||||
#include <assert.h>
|
||||
|
||||
// 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;
|
||||
}
|
||||
}
|
||||
}
|
391
MediaServer/libmov/source/mov-esds.c
Normal file
391
MediaServer/libmov/source/mov-esds.c
Normal file
@ -0,0 +1,391 @@
|
||||
#include "mov-internal.h"
|
||||
#include <assert.h>
|
||||
#include <stdlib.h>
|
||||
#include <errno.h>
|
||||
|
||||
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;
|
||||
}
|
54
MediaServer/libmov/source/mov-ftyp.c
Normal file
54
MediaServer/libmov/source/mov-ftyp.c
Normal file
@ -0,0 +1,54 @@
|
||||
#include "mov-internal.h"
|
||||
#include <assert.h>
|
||||
|
||||
// 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;
|
||||
}
|
43
MediaServer/libmov/source/mov-hdlr.c
Normal file
43
MediaServer/libmov/source/mov-hdlr.c
Normal file
@ -0,0 +1,43 @@
|
||||
#include "mov-internal.h"
|
||||
#include <string.h>
|
||||
#include <assert.h>
|
||||
|
||||
// 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);
|
||||
}
|
37
MediaServer/libmov/source/mov-hdr.c
Normal file
37
MediaServer/libmov/source/mov-hdr.c
Normal file
@ -0,0 +1,37 @@
|
||||
#include "mov-internal.h"
|
||||
#include <assert.h>
|
||||
#include <stdlib.h>
|
||||
#include <errno.h>
|
||||
|
||||
// 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);
|
||||
}
|
335
MediaServer/libmov/source/mov-internal.h
Normal file
335
MediaServer/libmov/source/mov-internal.h
Normal file
@ -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_ */
|
90
MediaServer/libmov/source/mov-iods.c
Normal file
90
MediaServer/libmov/source/mov-iods.c
Normal file
@ -0,0 +1,90 @@
|
||||
#include "mov-internal.h"
|
||||
#include <assert.h>
|
||||
#include <stdlib.h>
|
||||
#include <errno.h>
|
||||
|
||||
// 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;
|
||||
}
|
123
MediaServer/libmov/source/mov-ioutil.h
Normal file
123
MediaServer/libmov/source/mov-ioutil.h
Normal file
@ -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_ */
|
35
MediaServer/libmov/source/mov-leva.c
Normal file
35
MediaServer/libmov/source/mov-leva.c
Normal file
@ -0,0 +1,35 @@
|
||||
#include "mov-internal.h"
|
||||
#include <assert.h>
|
||||
|
||||
// 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);
|
||||
}
|
76
MediaServer/libmov/source/mov-mdhd.c
Normal file
76
MediaServer/libmov/source/mov-mdhd.c
Normal file
@ -0,0 +1,76 @@
|
||||
#include "mov-internal.h"
|
||||
#include <assert.h>
|
||||
|
||||
// 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;
|
||||
}
|
33
MediaServer/libmov/source/mov-mehd.c
Normal file
33
MediaServer/libmov/source/mov-mehd.c
Normal file
@ -0,0 +1,33 @@
|
||||
#include "mov-internal.h"
|
||||
#include <errno.h>
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
#include <assert.h>
|
||||
|
||||
// 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;
|
||||
}
|
22
MediaServer/libmov/source/mov-mfhd.c
Normal file
22
MediaServer/libmov/source/mov-mfhd.c
Normal file
@ -0,0 +1,22 @@
|
||||
#include "mov-internal.h"
|
||||
#include <errno.h>
|
||||
#include <stdlib.h>
|
||||
#include <assert.h>
|
||||
|
||||
// 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;
|
||||
}
|
166
MediaServer/libmov/source/mov-minf.c
Normal file
166
MediaServer/libmov/source/mov-minf.c
Normal file
@ -0,0 +1,166 @@
|
||||
#include "mov-internal.h"
|
||||
#include <assert.h>
|
||||
|
||||
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;
|
||||
}
|
125
MediaServer/libmov/source/mov-mvhd.c
Normal file
125
MediaServer/libmov/source/mov-mvhd.c
Normal file
@ -0,0 +1,125 @@
|
||||
#include "mov-internal.h"
|
||||
#include <assert.h>
|
||||
|
||||
// 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;
|
||||
}
|
77
MediaServer/libmov/source/mov-opus.c
Normal file
77
MediaServer/libmov/source/mov-opus.c
Normal file
@ -0,0 +1,77 @@
|
||||
#include "mov-internal.h"
|
||||
#include <assert.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <errno.h>
|
||||
|
||||
// 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;
|
||||
}
|
687
MediaServer/libmov/source/mov-reader.c
Normal file
687
MediaServer/libmov/source/mov-reader.c
Normal file
@ -0,0 +1,687 @@
|
||||
#include "mov-reader.h"
|
||||
#include "mov-internal.h"
|
||||
#include <errno.h>
|
||||
#include <stdio.h>
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <assert.h>
|
||||
|
||||
#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;
|
||||
}
|
73
MediaServer/libmov/source/mov-sidx.c
Normal file
73
MediaServer/libmov/source/mov-sidx.c
Normal file
@ -0,0 +1,73 @@
|
||||
#include "mov-internal.h"
|
||||
#include <assert.h>
|
||||
|
||||
// 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;
|
||||
}
|
174
MediaServer/libmov/source/mov-stco.c
Normal file
174
MediaServer/libmov/source/mov-stco.c
Normal file
@ -0,0 +1,174 @@
|
||||
#include "mov-internal.h"
|
||||
#include <errno.h>
|
||||
#include <stdlib.h>
|
||||
#include <assert.h>
|
||||
|
||||
// 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);
|
||||
}
|
86
MediaServer/libmov/source/mov-stsc.c
Normal file
86
MediaServer/libmov/source/mov-stsc.c
Normal file
@ -0,0 +1,86 @@
|
||||
#include "mov-internal.h"
|
||||
#include <errno.h>
|
||||
#include <stdlib.h>
|
||||
#include <assert.h>
|
||||
|
||||
// 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;
|
||||
}
|
576
MediaServer/libmov/source/mov-stsd.c
Normal file
576
MediaServer/libmov/source/mov-stsd.c
Normal file
@ -0,0 +1,576 @@
|
||||
#include "mov-internal.h"
|
||||
#include <assert.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <errno.h>
|
||||
|
||||
// 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;
|
||||
}
|
79
MediaServer/libmov/source/mov-stss.c
Normal file
79
MediaServer/libmov/source/mov-stss.c
Normal file
@ -0,0 +1,79 @@
|
||||
#include "mov-internal.h"
|
||||
#include <stdlib.h>
|
||||
#include <assert.h>
|
||||
#include <errno.h>
|
||||
|
||||
// 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;
|
||||
}
|
||||
}
|
131
MediaServer/libmov/source/mov-stsz.c
Normal file
131
MediaServer/libmov/source/mov-stsz.c
Normal file
@ -0,0 +1,131 @@
|
||||
#include "mov-internal.h"
|
||||
#include <errno.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <assert.h>
|
||||
|
||||
// 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;
|
||||
}
|
243
MediaServer/libmov/source/mov-stts.c
Normal file
243
MediaServer/libmov/source/mov-stts.c
Normal file
@ -0,0 +1,243 @@
|
||||
#include "mov-internal.h"
|
||||
#include <errno.h>
|
||||
#include <stdlib.h>
|
||||
#include <assert.h>
|
||||
|
||||
// 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);
|
||||
}
|
63
MediaServer/libmov/source/mov-tag.c
Normal file
63
MediaServer/libmov/source/mov-tag.c
Normal file
@ -0,0 +1,63 @@
|
||||
#include "mov-internal.h"
|
||||
#include <stdint.h>
|
||||
|
||||
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;
|
||||
}
|
48
MediaServer/libmov/source/mov-tfdt.c
Normal file
48
MediaServer/libmov/source/mov-tfdt.c
Normal file
@ -0,0 +1,48 @@
|
||||
#include "mov-internal.h"
|
||||
#include <errno.h>
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
#include <assert.h>
|
||||
|
||||
// 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;
|
||||
}
|
98
MediaServer/libmov/source/mov-tfhd.c
Normal file
98
MediaServer/libmov/source/mov-tfhd.c
Normal file
@ -0,0 +1,98 @@
|
||||
#include "mov-internal.h"
|
||||
#include <assert.h>
|
||||
|
||||
// 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;
|
||||
}
|
90
MediaServer/libmov/source/mov-tfra.c
Normal file
90
MediaServer/libmov/source/mov-tfra.c
Normal file
@ -0,0 +1,90 @@
|
||||
#include "mov-internal.h"
|
||||
#include <stdlib.h>
|
||||
#include <assert.h>
|
||||
#include <errno.h>
|
||||
|
||||
// 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;
|
||||
}
|
135
MediaServer/libmov/source/mov-tkhd.c
Normal file
135
MediaServer/libmov/source/mov-tkhd.c
Normal file
@ -0,0 +1,135 @@
|
||||
#include "mov-internal.h"
|
||||
#include <assert.h>
|
||||
|
||||
// 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;
|
||||
}
|
385
MediaServer/libmov/source/mov-track.c
Normal file
385
MediaServer/libmov/source/mov-track.c
Normal file
@ -0,0 +1,385 @@
|
||||
#include "mov-internal.h"
|
||||
#include <errno.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <assert.h>
|
||||
|
||||
#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;
|
||||
}
|
37
MediaServer/libmov/source/mov-trex.c
Normal file
37
MediaServer/libmov/source/mov-trex.c
Normal file
@ -0,0 +1,37 @@
|
||||
#include "mov-internal.h"
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <assert.h>
|
||||
|
||||
// 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;
|
||||
}
|
163
MediaServer/libmov/source/mov-trun.c
Normal file
163
MediaServer/libmov/source/mov-trun.c
Normal file
@ -0,0 +1,163 @@
|
||||
#include "mov-internal.h"
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <assert.h>
|
||||
#include <errno.h>
|
||||
|
||||
// 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;
|
||||
}
|
115
MediaServer/libmov/source/mov-tx3g.c
Normal file
115
MediaServer/libmov/source/mov-tx3g.c
Normal file
@ -0,0 +1,115 @@
|
||||
#include "mov-internal.h"
|
||||
#include <assert.h>
|
||||
#include <stdlib.h>
|
||||
#include <errno.h>
|
||||
|
||||
// 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;
|
||||
}
|
148
MediaServer/libmov/source/mov-udta.c
Normal file
148
MediaServer/libmov/source/mov-udta.c
Normal file
@ -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;
|
||||
}
|
40
MediaServer/libmov/source/mov-vpcc.c
Normal file
40
MediaServer/libmov/source/mov-vpcc.c
Normal file
@ -0,0 +1,40 @@
|
||||
#include "mov-internal.h"
|
||||
#include <assert.h>
|
||||
#include <stdlib.h>
|
||||
#include <errno.h>
|
||||
|
||||
// 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;
|
||||
}
|
343
MediaServer/libmov/source/mov-writer.c
Normal file
343
MediaServer/libmov/source/mov-writer.c
Normal file
@ -0,0 +1,343 @@
|
||||
#include "mov-writer.h"
|
||||
#include "mov-internal.h"
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <assert.h>
|
||||
#include <errno.h>
|
||||
#include <time.h>
|
||||
|
||||
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;
|
||||
}
|
289
MediaServer/libmpeg/include/mpeg-element-descriptor.h
Normal file
289
MediaServer/libmpeg/include/mpeg-element-descriptor.h
Normal file
@ -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_ */
|
63
MediaServer/libmpeg/include/mpeg-muxer.h
Normal file
63
MediaServer/libmpeg/include/mpeg-muxer.h
Normal file
@ -0,0 +1,63 @@
|
||||
#ifndef _mpeg_muxer_h_
|
||||
#define _mpeg_muxer_h_
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stddef.h>
|
||||
#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_ */
|
6
MediaServer/libmpeg/include/mpeg-pes-proto.h
Normal file
6
MediaServer/libmpeg/include/mpeg-pes-proto.h
Normal file
@ -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_ */
|
156
MediaServer/libmpeg/include/mpeg-proto.h
Normal file
156
MediaServer/libmpeg/include/mpeg-proto.h
Normal file
@ -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_ */
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user