From 4bc6457e4d02a02cf73e6a6df197dff2efc30d8b Mon Sep 17 00:00:00 2001 From: ireader Date: Sat, 7 Sep 2024 10:05:11 +0800 Subject: [PATCH] feat: #353 enhanced rtmp v2 audio(don't support mutli-channel) --- libflv/include/flv-header.h | 5 ++ libflv/include/flv-muxer.h | 3 + libflv/include/flv-proto.h | 39 ++++++++- libflv/include/xiph-flac.h | 32 +++++++ libflv/libflv.vcxproj | 2 + libflv/libflv.vcxproj.filters | 6 ++ libflv/source/flv-demuxer.c | 25 ++++-- libflv/source/flv-header.c | 146 ++++++++++++++++++++++++++++++- libflv/source/flv-muxer.c | 36 ++++++++ libflv/source/xiph-flac.c | 157 ++++++++++++++++++++++++++++++++++ 10 files changed, 439 insertions(+), 12 deletions(-) create mode 100644 libflv/include/xiph-flac.h create mode 100644 libflv/source/xiph-flac.c diff --git a/libflv/include/flv-header.h b/libflv/include/flv-header.h index 1dfa3696..bbf07d42 100644 --- a/libflv/include/flv-header.h +++ b/libflv/include/flv-header.h @@ -33,6 +33,11 @@ struct flv_audio_tag_header_t 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 + + uint8_t multitrack; /// enhanced rtmp v2, e.g. FLV_AUDIO_MULTI_TRACK_MANY_CODECS + uint8_t channelorder; /// enhanced rtmp v2, valid on FLV_AUDIO_PACKET_TYPE_MULTICHANNEL_CONFIG + uint32_t channelflags; /// enhanced rtmp v2, valid on FLV_AUDIO_PACKET_TYPE_MULTICHANNEL_CONFIG + uint8_t channelmapping[8]; /// enhanced rtmp v2, valid on FLV_AUDIO_PACKET_TYPE_MULTICHANNEL_CONFIG }; struct flv_video_tag_header_t diff --git a/libflv/include/flv-muxer.h b/libflv/include/flv-muxer.h index 1fb51615..9d69fed7 100644 --- a/libflv/include/flv-muxer.h +++ b/libflv/include/flv-muxer.h @@ -39,6 +39,9 @@ int flv_muxer_g711u(flv_muxer_t* muxer, const void* data, size_t bytes, uint32_t /// @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); +int flv_muxer_ac3(flv_muxer_t* muxer, const void* data, size_t bytes, uint32_t pts, uint32_t dts); +int flv_muxer_eac3(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); diff --git a/libflv/include/flv-proto.h b/libflv/include/flv-proto.h index 1eb43828..fa26b1a8 100644 --- a/libflv/include/flv-proto.h +++ b/libflv/include/flv-proto.h @@ -11,15 +11,21 @@ #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_FLAC (4 << 4) // Nellymoser16KMono -> FLAC(enhanced rtmp v2) +#define FLV_AUDIO_EAC3 (5 << 4) // Nellymoser8KMono -> EAC3(enhanced rtmp v2) +#define FLV_AUDIO_Nelly (6 << 4) // Nellymoser #define FLV_AUDIO_G711A (7 << 4) // G711 A-law #define FLV_AUDIO_G711U (8 << 4) // G711 mu-law +#define FLV_AUDIO_FOURCC (9 << 4) // enhanced rtmp v2 #define FLV_AUDIO_AAC (10 << 4) #define FLV_AUDIO_SPEEX (11 << 4) +#define FLV_AUDIO_AC3 (12 << 4) // enhanced rtmp v2 #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 +#define FLV_AUDIO_OPUS_HEAD (0x1100 | FLV_AUDIO_OPUS) // opus-codec.org +#define FLV_AUDIO_FLAC_HEAD (0x1200 | FLV_AUDIO_FLAC) // xiph.org/flac // FLV Video Type #define FLV_VIDEO_H263 2 // Sorenson H.263 @@ -64,6 +70,13 @@ enum // note: PacketTypeSequenceStart and PacketTypeMPEG2TSSequenceStart // are mutually exclusive FLV_PACKET_TYPE_MPEG2TS_SEQUENCE_START = 5, + + // Turns on video multitrack mode + FLV_PACKET_TYPE_MULTITRACK = 6, + + // audio + FLV_AUDIO_PACKET_TYPE_MULTICHANNEL_CONFIG = 4, + FLV_AUDIO_PACKET_TYPE_MULTITRACK = 5, // Turns on audio multitrack mode }; enum @@ -95,11 +108,29 @@ enum FLV_SOUND_CHANNEL_STEREO = 1, // 2-channels }; +// AvMultitrackType +enum flv_audio_multi_track_e +{ + FLV_AUDIO_MULTI_TRACK_ONE = 0, // OneTrack + FLV_AUDIO_MULTI_TRACK_MANY = 1, // ManyTracks + FLV_AUDIO_MULTI_TRACK_MANY_CODECS = 2, // ManyTracksManyCodecs + + FLV_AUDIO_MULTI_TRACK_NONE = 255, +}; + #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') +#define FLV_VIDEO_FOURCC_AV1 FLV_VIDEO_FOURCC('a', 'v', '0', '1') +#define FLV_VIDEO_FOURCC_AVC FLV_VIDEO_FOURCC('a', 'v', 'c', '1') // H.264 +#define FLV_VIDEO_FOURCC_HEVC FLV_VIDEO_FOURCC('h', 'v', 'c', '1') // H.265 +#define FLV_VIDEO_FOURCC_VVC FLV_VIDEO_FOURCC('v', 'v', 'c', '1') // H.266 + +#define FLV_AUDIO_FOURCC_AC3 FLV_VIDEO_FOURCC('a', 'c', '-', '3') // AC-3/E-AC-3 - +#define FLV_AUDIO_FOURCC_EAC3 FLV_VIDEO_FOURCC('e', 'c', '-', '3') +#define FLV_AUDIO_FOURCC_OPUS FLV_VIDEO_FOURCC('O', 'p', 'u', 's') // Opus audio - +#define FLV_AUDIO_FOURCC_MP3 FLV_VIDEO_FOURCC('.', 'm', 'p', '3') // Mp3 audio - +#define FLV_AUDIO_FOURCC_FLAC FLV_VIDEO_FOURCC('f', 'L', 'a', 'C') // Free Lossless Audio Codec - +#define FLV_AUDIO_FOURCC_AAC FLV_VIDEO_FOURCC('m', 'p', '4', 'a') // Advanced Audio Coding - #endif /* !_flv_proto_h_ */ diff --git a/libflv/include/xiph-flac.h b/libflv/include/xiph-flac.h new file mode 100644 index 00000000..d987b223 --- /dev/null +++ b/libflv/include/xiph-flac.h @@ -0,0 +1,32 @@ +#ifndef _xiph_flac_h_ +#define _xiph_flac_h_ + +#include +#include + +#if defined(__cplusplus) +extern "C" { +#endif + +struct flac_streaminfo_t +{ + uint16_t min_block_size; // in samples + uint16_t max_block_size; + uint32_t min_frame_size; // 24-bits, in bytes + uint32_t max_frame_size; + uint64_t sample_rate : 20; // 20-bits, [0, 655350Hz] + uint64_t channels : 3; // (number of channels)-1, 3-bits, [1, 8] + uint64_t bits_per_sample : 5; // (bits per sample)-1, 5-bits, [4, 32] + uint64_t samples : 36; // total samples in stream + uint8_t signature[16]; // MD5 signature of the unencoded audio data +}; + +/// @return >0-ok, <=0-error +int flac_streaminfo_save(const struct flac_streaminfo_t* flac, uint8_t* data, size_t bytes); +/// @return >0-ok, <=0-error +int flac_streaminfo_load(const uint8_t* data, size_t bytes, struct flac_streaminfo_t* flac); + +#if defined(__cplusplus) +} +#endif +#endif /* !_xiph_flac_h_ */ diff --git a/libflv/libflv.vcxproj b/libflv/libflv.vcxproj index eea75ab2..f2307b9d 100644 --- a/libflv/libflv.vcxproj +++ b/libflv/libflv.vcxproj @@ -39,6 +39,7 @@ + @@ -67,6 +68,7 @@ + {D5BA0BB6-0D84-48CB-8630-F617CB6DE375} diff --git a/libflv/libflv.vcxproj.filters b/libflv/libflv.vcxproj.filters index 3e14439c..6b062133 100644 --- a/libflv/libflv.vcxproj.filters +++ b/libflv/libflv.vcxproj.filters @@ -75,6 +75,9 @@ Header Files + + Header Files + @@ -155,5 +158,8 @@ Source Files + + Source Files + \ No newline at end of file diff --git a/libflv/source/flv-demuxer.c b/libflv/source/flv-demuxer.c index 5f4eed71..6b0e7952 100644 --- a/libflv/source/flv-demuxer.c +++ b/libflv/source/flv-demuxer.c @@ -6,6 +6,7 @@ #include "mpeg4-hevc.h" #include "mpeg4-vvc.h" #include "opus-head.h" +#include "xiph-flac.h" #include "aom-av1.h" #include "avswg-avs3.h" #include "amf0.h" @@ -20,6 +21,7 @@ struct flv_demuxer_t { struct mpeg4_aac_t aac; struct opus_head_t opus; + struct flac_streaminfo_t flac; } a; union @@ -101,7 +103,7 @@ static int flv_demuxer_audio(struct flv_demuxer_t* flv, const uint8_t* data, int 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 + else if (FLV_AVPACKET == audio.avpacket) { if (0 != flv_demuxer_check_and_alloc(flv, bytes + 7 + 1 + flv->a.aac.npce)) return -ENOMEM; @@ -123,20 +125,31 @@ static int flv_demuxer_audio(struct flv_demuxer_t* flv, const uint8_t* data, int 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 + else if (FLV_AVPACKET == audio.avpacket) { 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) + else if (FLV_AUDIO_FLAC == audio.codecid) { - return flv->handler(flv->param, audio.codecid, data + n, bytes - n, timestamp, timestamp, 0); + if (FLV_SEQUENCE_HEADER == audio.avpacket) + { + flac_streaminfo_load(data + n, bytes - n, &flv->a.flac); + return flv->handler(flv->param, FLV_AUDIO_FLAC_HEAD, data + n, bytes - n, timestamp, timestamp, 0); + } + else if (FLV_AVPACKET == audio.avpacket) + { + 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); + // Audio frame data: mp3/ac-3/eac-3 + if (FLV_AVPACKET == audio.avpacket) + return flv->handler(flv->param, audio.codecid, data + n, bytes - n, timestamp, timestamp, 0); } + + return 0; } static int flv_demuxer_video(struct flv_demuxer_t* flv, const uint8_t* data, int bytes, uint32_t timestamp) diff --git a/libflv/source/flv-header.c b/libflv/source/flv-header.c index e6ce3060..dbc34e1d 100644 --- a/libflv/source/flv-header.c +++ b/libflv/source/flv-header.c @@ -1,5 +1,7 @@ #include "flv-header.h" #include "flv-proto.h" +#include +#include #include #include @@ -67,14 +69,49 @@ int flv_tag_header_read(struct flv_tag_header_t* tag, const uint8_t* buf, size_t return FLV_TAG_HEADER_SIZE; } +static int flv_audio_tag_header_read_fourcc(struct flv_audio_tag_header_t* audio, const uint8_t* buf, size_t len) +{ + if (len < 4) + return -1; + + switch (FLV_VIDEO_FOURCC(buf[0], buf[1], buf[2], buf[3])) + { + case FLV_AUDIO_FOURCC_AC3: + audio->codecid = FLV_AUDIO_AC3; + break; + case FLV_AUDIO_FOURCC_EAC3: + audio->codecid = FLV_AUDIO_EAC3; + break; + case FLV_AUDIO_FOURCC_OPUS: + audio->codecid = FLV_AUDIO_OPUS; + break; + case FLV_AUDIO_FOURCC_MP3: + audio->codecid = FLV_AUDIO_MP3; + break; + case FLV_AUDIO_FOURCC_FLAC: + audio->codecid = FLV_AUDIO_FLAC; + break; + case FLV_AUDIO_FOURCC_AAC: + audio->codecid = FLV_AUDIO_AAC; + break; + default: + audio->codecid = FLV_AUDIO_AAC; // unknown + break; + } + + return 0; +} + int flv_audio_tag_header_read(struct flv_audio_tag_header_t* audio, const uint8_t* buf, size_t len) { + size_t off; 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; + audio->multitrack = FLV_AUDIO_MULTI_TRACK_NONE; if (FLV_AUDIO_AAC == audio->codecid || FLV_AUDIO_OPUS == audio->codecid) { @@ -87,6 +124,72 @@ int flv_audio_tag_header_read(struct flv_audio_tag_header_t* audio, const uint8_ assert(FLV_SEQUENCE_HEADER == audio->avpacket || FLV_AVPACKET == audio->avpacket); return 2; } + else if (FLV_AUDIO_FOURCC == audio->codecid) + { + audio->avpacket = buf[0] & 0x0F; + if (audio->avpacket == FLV_AUDIO_PACKET_TYPE_MULTITRACK) + { + if (len < 2) + { + assert(0); + return -1; + } + + audio->multitrack = (buf[1] & 0xF0) >> 4; + audio->avpacket = buf[1] & 0x0f; + // for ManyTracksManyCodecs, get the first codec + if (0 != flv_audio_tag_header_read_fourcc(audio, buf + 2, len - 2)) + { + assert(0); + return -1; + } + off = 6; + } + else + { + if (0 != flv_audio_tag_header_read_fourcc(audio, buf + 1, len - 1)) + { + assert(0); + return -1; + } + off = 5; + } + + // TODO: multi-track loop + + if (audio->avpacket == FLV_AUDIO_PACKET_TYPE_MULTICHANNEL_CONFIG) + { + if(off + 2 > len) + { + assert(0); + return -1; + } + audio->channelorder = buf[off++]; + audio->channels = buf[off++]; + if (1 == audio->channelorder) + { + if (off + 4 > len) + { + assert(0); + return -1; + } + audio->channelflags = (((uint32_t)buf[off + 0]) << 24) | (((uint32_t)buf[off + 1]) << 16) | (((uint32_t)buf[off + 2]) << 8) | (uint32_t)buf[off + 3]; + off += 4; + } + else if (2 == audio->channelorder) + { + if (off + audio->channels > len) + { + assert(0); + return -1; + } + memcpy(audio->channelmapping, buf, audio->channels <= sizeof(audio->channelmapping) / sizeof(audio->channelmapping[0]) ? audio->channels : (sizeof(audio->channelmapping) / sizeof(audio->channelmapping[0]))); + off += audio->channels; + } + } + + return (int)off; + } else { return 1; @@ -217,16 +320,55 @@ int flv_tag_header_write(const struct flv_tag_header_t* tag, uint8_t* buf, 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)) + if ((int)len < 1 + ((FLV_AUDIO_AAC == audio->codecid)? 1 : 0)) return -1; - if (FLV_AUDIO_AAC == audio->codecid || FLV_AUDIO_OPUS == audio->codecid) + if (FLV_AUDIO_AAC == 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 if (FLV_AUDIO_OPUS == audio->codecid || FLV_AUDIO_FLAC == audio->codecid || FLV_AUDIO_AC3 == audio->codecid || FLV_AUDIO_EAC3 == audio->codecid) + { + if (len < 5) + return -1; + + assert(FLV_SEQUENCE_HEADER == audio->avpacket || FLV_AVPACKET == audio->avpacket); + buf[0] = FLV_AUDIO_FOURCC | audio->avpacket; + switch (audio->codecid) + { + case FLV_AUDIO_FLAC: + buf[1] = (FLV_AUDIO_FOURCC_FLAC >> 24) & 0xFF; + buf[2] = (FLV_AUDIO_FOURCC_FLAC >> 16) & 0xFF; + buf[3] = (FLV_AUDIO_FOURCC_FLAC >> 8) & 0xFF; + buf[4] = (FLV_AUDIO_FOURCC_FLAC) & 0xFF; + break; + + case FLV_AUDIO_AC3: + buf[1] = (FLV_AUDIO_FOURCC_AC3 >> 24) & 0xFF; + buf[2] = (FLV_AUDIO_FOURCC_AC3 >> 16) & 0xFF; + buf[3] = (FLV_AUDIO_FOURCC_AC3 >> 8) & 0xFF; + buf[4] = (FLV_AUDIO_FOURCC_AC3) & 0xFF; + break; + + case FLV_AUDIO_EAC3: + buf[1] = (FLV_AUDIO_FOURCC_EAC3 >> 24) & 0xFF; + buf[2] = (FLV_AUDIO_FOURCC_EAC3 >> 16) & 0xFF; + buf[3] = (FLV_AUDIO_FOURCC_EAC3 >> 8) & 0xFF; + buf[4] = (FLV_AUDIO_FOURCC_EAC3) & 0xFF; + break; + + case FLV_AUDIO_OPUS: + buf[1] = (FLV_AUDIO_FOURCC_OPUS >> 24) & 0xFF; + buf[2] = (FLV_AUDIO_FOURCC_OPUS >> 16) & 0xFF; + buf[3] = (FLV_AUDIO_FOURCC_OPUS >> 8) & 0xFF; + buf[4] = (FLV_AUDIO_FOURCC_OPUS) & 0xFF; + break; + } + return 5; + } else { buf[0] = (audio->codecid /* <<4 */) | ((audio->rate & 0x03) << 2) | ((audio->bits & 0x01) << 1) | (audio->channels & 0x01); diff --git a/libflv/source/flv-muxer.c b/libflv/source/flv-muxer.c index 87df4ead..0570dd21 100644 --- a/libflv/source/flv-muxer.c +++ b/libflv/source/flv-muxer.c @@ -264,6 +264,42 @@ int flv_muxer_opus(flv_muxer_t* flv, const void* data, size_t bytes, uint32_t pt return flv->handler(flv->param, FLV_TYPE_AUDIO, flv->ptr, bytes + m, dts); } +int flv_muxer_ac3(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.codecid = FLV_AUDIO_AC3; + 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_eac3(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.codecid = FLV_AUDIO_EAC3; + 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); +} + static int flv_muxer_h264(struct flv_muxer_t* flv, uint32_t pts, uint32_t dts) { int r; diff --git a/libflv/source/xiph-flac.c b/libflv/source/xiph-flac.c new file mode 100644 index 00000000..d98226e2 --- /dev/null +++ b/libflv/source/xiph-flac.c @@ -0,0 +1,157 @@ +#include "xiph-flac.h" +#include +#include +#include + +// https://xiph.org/flac/format.html#metadata_block_data +/* +STREAM +------------------------------------------------------------------------------------------------------------------------- +<32> "fLaC", the FLAC stream marker in ASCII, meaning byte 0 of the stream is 0x66, followed by 0x4C 0x61 0x43 +METADATA_BLOCK This is the mandatory STREAMINFO metadata block that has the basic properties of the stream +METADATA_BLOCK* Zero or more metadata blocks +FRAME+ One or more audio frames + + +METADATA_BLOCK +------------------------------------------------------------------------------------------------------------------------- +METADATA_BLOCK_HEADER A block header that specifies the type and size of the metadata block data. +METADATA_BLOCK_DATA + + +METADATA_BLOCK_HEADER +------------------------------------------------------------------------------------------------------------------------- +<1> Last-metadata-block flag: '1' if this block is the last metadata block before the audio blocks, '0' otherwise. +<7> BLOCK_TYPE + 0 : STREAMINFO + 1 : PADDING + 2 : APPLICATION + 3 : SEEKTABLE + 4 : VORBIS_COMMENT + 5 : CUESHEET + 6 : PICTURE + 7-126 : reserved + 127 : invalid, to avoid confusion with a frame sync code +<24> Length (in bytes) of metadata to follow (does not include the size of the METADATA_BLOCK_HEADER) + + +METADATA_BLOCK_DATA +------------------------------------------------------------------------------------------------------------------------- +METADATA_BLOCK_STREAMINFO +|| METADATA_BLOCK_PADDING +|| METADATA_BLOCK_APPLICATION +|| METADATA_BLOCK_SEEKTABLE +|| METADATA_BLOCK_VORBIS_COMMENT +|| METADATA_BLOCK_CUESHEET +|| METADATA_BLOCK_PICTURE The block data must match the block type in the block header. + + +METADATA_BLOCK_STREAMINFO +------------------------------------------------------------------------------------------------------------------------- +<16> The minimum block size (in samples) used in the stream. +<16> The maximum block size (in samples) used in the stream. (Minimum blocksize == maximum blocksize) implies a fixed-blocksize stream. +<24> The minimum frame size (in bytes) used in the stream. May be 0 to imply the value is not known. +<24> The maximum frame size (in bytes) used in the stream. May be 0 to imply the value is not known. +<20> Sample rate in Hz. Though 20 bits are available, the maximum sample rate is limited by the structure of frame headers to 655350Hz. Also, a value of 0 is invalid. +<3> (number of channels)-1. FLAC supports from 1 to 8 channels +<5> (bits per sample)-1. FLAC supports from 4 to 32 bits per sample. +<36> Total samples in stream. 'Samples' means inter-channel sample, i.e. one second of 44.1Khz audio will have 44100 samples regardless of the number of channels. A value of zero here means the number of total samples is unknown. +<128> MD5 signature of the unencoded audio data. This allows the decoder to determine if an error exists in the audio data even when the error does not result in an invalid bitstream. + +NOTES +FLAC specifies a minimum block size of 16 and a maximum block size of 65535, meaning the bit patterns corresponding to the numbers 0-15 in the minimum blocksize and maximum blocksize fields are invalid. +*/ + + +int flac_streaminfo_save(const struct flac_streaminfo_t* flac, uint8_t* data, size_t bytes) +{ + if (bytes < 34 + 4 + 4) + return -1; + + memcpy(data, "fLaC", 4); + data[4] = 0x00 | 0; // BLOCK_TYPE: STREAMINFO + data[5] = 0; + data[6] = 0; + data[7] = 34; // stream info block length + + // METADATA_BLOCK_STREAMINFO + data[8] = (uint8_t)(flac->min_block_size >> 8); + data[9] = (uint8_t)flac->min_block_size; + data[10] = (uint8_t)(flac->max_block_size >> 8); + data[11] = (uint8_t)flac->max_block_size; + data[12] = (uint8_t)(flac->min_frame_size >> 16); + data[13] = (uint8_t)(flac->min_frame_size >> 8); + data[14] = (uint8_t)(flac->min_frame_size >> 0); + data[15] = (uint8_t)(flac->max_frame_size >> 16); + data[16] = (uint8_t)(flac->max_frame_size >> 8); + data[17] = (uint8_t)(flac->max_frame_size >> 0); + data[18] = (uint8_t)(flac->sample_rate >> 12); + data[19] = (uint8_t)(flac->sample_rate >> 4); + data[20] = (uint8_t)((flac->sample_rate & 0x0F) << 4) | ((flac->channels & 0x07) << 1) | ((flac->bits_per_sample >> 4) & 0x01); + data[21] = (uint8_t)((flac->bits_per_sample & 0x0F) << 4) | ((flac->samples >> 32) & 0x0F); + data[22] = (uint8_t)(flac->samples >> 24); + data[23] = (uint8_t)(flac->samples >> 16); + data[24] = (uint8_t)(flac->samples >> 8); + data[25] = (uint8_t)(flac->samples >> 0); + memcpy(data + 26, flac->signature, 16); + return 34 + 4 + 4; +} + +int flac_streaminfo_load(const uint8_t* data, size_t bytes, struct flac_streaminfo_t* flac) +{ + int n = 0; + uint32_t len; + const uint8_t* ptr; + + ptr = data; + + // FLAC stream marker + if (bytes > 4 && 0 == memcmp(data, "fLaC", 4)) + { + n = 4; + data += 4; + bytes -= 4; + } + + // METADATA_BLOCK_HEADER + for (; bytes > 4; bytes -= 4 + len, data += 4 + len) + { + len = (((uint32_t)data[1]) << 16) | (((uint32_t)data[2]) << 8) | (uint32_t)data[3]; // 24bits length + if (bytes < 4 + len) + return -1; + + // METADATA_BLOCK_HEADER + if ((data[0] & 0x7F) != 0 /*STREAMINFO*/) + continue; + + if (len < 34) + return -1; + + memset(flac, 0, sizeof(*flac)); + flac->min_block_size = ((uint16_t)data[4] << 8) | data[5]; + flac->max_block_size = ((uint16_t)data[6] << 8) | data[7]; + flac->min_frame_size = ((uint32_t)data[8] << 16) | ((uint32_t)data[9] << 8) | data[10]; + flac->max_frame_size = ((uint32_t)data[11] << 16) | ((uint32_t)data[12] << 8) | data[13]; + flac->sample_rate = (((uint32_t)data[14] << 16) | ((uint32_t)data[15] << 8) | data[16]) >> 4; + flac->channels = ((data[16] >> 1) & 0x07); + flac->bits_per_sample = ((data[16] & 0x01) << 4) + (data[17] >> 4); + flac->samples = ((((uint64_t)data[17]) & 0x0F) << 32) | (((uint64_t)data[18] << 24) | ((uint64_t)data[19] << 16) | ((uint64_t)data[20] << 8) | (uint64_t)data[21]); + memcpy(flac->signature, data + 22, 16); + return (int)(data + 4 + len - ptr); + } + + return -1; +} + +#if defined(DEBUG) || defined(_DEBUG) +void flac_streaminfo_test(void) +{ + uint8_t data[42]; + const uint8_t src[] = {0x66, 0x4C, 0x61, 0x43, 0x00, 0x00, 0x00, 0x22, 0x04, 0x80, 0x04, 0x80, 0x00, 0x06, 0x72, 0x00, 0x17, 0xF2, 0x17, 0x70, 0x03, 0x70, 0x00, 0x3A, 0x69, 0x80, 0xE5, 0xD1, 0x00, 0xC6, 0x3F, 0x51, 0x88, 0x90, 0x0C, 0x66, 0xB6, 0xA6, 0xA0, 0x8C, 0xE2, 0xEB, }; + + struct flac_streaminfo_t flac; + assert(sizeof(src) == flac_streaminfo_load(src, sizeof(src), &flac)); + assert(sizeof(src) == flac_streaminfo_save(&flac, data, sizeof(data))); + assert(0 == memcmp(src, data, sizeof(src))); +} +#endif