add codec.
All checks were successful
Deploy / PullDocker (push) Successful in 11s
Deploy / Build (push) Successful in 1m54s

This commit is contained in:
amass 2024-09-30 16:12:57 +00:00
parent 9de3af15eb
commit 08340ad5c8
131 changed files with 23034 additions and 1 deletions

View File

@ -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
)

View 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_ */

View 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_ */

View 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_ */

View 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_ */

View 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_ */

View 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_ */

View 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_ */

View 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_ */

View 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_ */

View 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_ */

View 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_ */

View 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_ */

View 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_ */

View 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_ */

View 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_ */

View 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_ */

View 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 */

View 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_ */

View 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_ */

View 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_ */

View 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

View 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;
}
}

View 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

View 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

View 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;
}

View 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;
}
}

View 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;
}

View 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);
}

View 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;
}

View 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;
}

View 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);
}

View 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

View 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;
}

View 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

View 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);
}

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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;
}

View 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

View 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;
}

View 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

View 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_ */

View 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_ */

View 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_ */

View 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_ */

View 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_ */

View 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_ */

View 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_*/

View 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_ */

View 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_ */

View 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_ */

View 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
}

View 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);
}

View 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;
}

View 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;
}

View 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;
}
}
}

View 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;
}

View 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;
}

View 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);
}

View 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);
}

View 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_ */

View 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;
}

View 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_ */

View 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);
}

View 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;
}

View 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;
}

View 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;
}

View 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;
}

View 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;
}

View 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;
}

View 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;
}

View 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;
}

View 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);
}

View 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;
}

View 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;
}

View 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;
}
}

View 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;
}

View 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);
}

View 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;
}

View 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;
}

View 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;
}

View 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;
}

View 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;
}

View 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;
}

View 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;
}

View 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;
}

View 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;
}

View 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;
}

View 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;
}

View 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;
}

View 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_ */

View 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_ */

View 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_ */

View 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