From a7183aedc50f3c1a15db38bdf97822c7f80f806b Mon Sep 17 00:00:00 2001 From: Ramkoemar Date: Thu, 6 Oct 2022 15:08:28 +0200 Subject: [PATCH] FLAC support: - FLAC container input and output support - FLAC container analyser - FLAC codec support in EBML input and output --- CMakeLists.txt | 4 + lib/checksum.h | 86 ++++++++ lib/dtsc.cpp | 6 +- lib/flac.cpp | 128 ++++++++++++ lib/flac.h | 32 +++ lib/meson.build | 2 + src/analysers/analyser_flac.cpp | 183 +++++++++++++++++ src/analysers/analyser_flac.h | 34 ++++ src/analysers/meson.build | 1 + src/input/input_ebml.cpp | 7 + src/input/input_flac.cpp | 347 ++++++++++++++++++++++++++++++++ src/input/input_flac.h | 48 +++++ src/input/meson.build | 1 + src/output/meson.build | 1 + src/output/output_ebml.cpp | 2 + src/output/output_flac.cpp | 50 +++++ src/output/output_flac.h | 17 ++ 17 files changed, 948 insertions(+), 1 deletion(-) create mode 100644 lib/flac.cpp create mode 100644 lib/flac.h create mode 100644 src/analysers/analyser_flac.cpp create mode 100644 src/analysers/analyser_flac.h create mode 100644 src/input/input_flac.cpp create mode 100644 src/input/input_flac.h create mode 100644 src/output/output_flac.cpp create mode 100644 src/output/output_flac.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 42f66781..27223eeb 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -386,6 +386,7 @@ makeAnalyser(H264 h264) #LTS makeAnalyser(HLS hls) #LTS makeAnalyser(RIFF riff) #LTS makeAnalyser(RTSP rtsp) #LTS +makeAnalyser(FLAC flac) #LTS_START ######################################## @@ -462,6 +463,8 @@ makeInput(HLS hls) makeInput(DTSC dtsc) makeInput(MP3 mp3) makeInput(FLV flv) +makeInput(FLAC flac) + option(WITH_AV "Build a generic libav-based input (not distributable!)") if (WITH_AV) makeInput(AV av) @@ -574,6 +577,7 @@ makeOutput(EBML ebml) makeOutput(RTSP rtsp)#LTS makeOutput(WAV wav)#LTS makeOutput(SDP sdp http) +makeOutput(FLAC flac http) add_executable(MistSession ${BINARY_DIR}/mist/.headers diff --git a/lib/checksum.h b/lib/checksum.h index 5568c89f..87b6515c 100644 --- a/lib/checksum.h +++ b/lib/checksum.h @@ -1,4 +1,5 @@ #include +#include "defines.h" namespace checksum{ inline unsigned int crc32c(unsigned int crc, const char *data, size_t len){ @@ -143,4 +144,89 @@ namespace checksum{ while (tmpData < end){crc = table[((unsigned char)crc) ^ *tmpData++] ^ (crc >> 8);} return crc; } + + inline unsigned int crc16(unsigned int crc, const char *data, size_t len){ + static const unsigned short table[] = { + 0x0000, 0x8005, 0x800F, 0x000A, 0x801B, 0x001E, 0x0014, 0x8011, + 0x8033, 0x0036, 0x003C, 0x8039, 0x0028, 0x802D, 0x8027, 0x0022, + 0x8063, 0x0066, 0x006C, 0x8069, 0x0078, 0x807D, 0x8077, 0x0072, + 0x0050, 0x8055, 0x805F, 0x005A, 0x804B, 0x004E, 0x0044, 0x8041, + 0x80C3, 0x00C6, 0x00CC, 0x80C9, 0x00D8, 0x80DD, 0x80D7, 0x00D2, + 0x00F0, 0x80F5, 0x80FF, 0x00FA, 0x80EB, 0x00EE, 0x00E4, 0x80E1, + 0x00A0, 0x80A5, 0x80AF, 0x00AA, 0x80BB, 0x00BE, 0x00B4, 0x80B1, + 0x8093, 0x0096, 0x009C, 0x8099, 0x0088, 0x808D, 0x8087, 0x0082, + 0x8183, 0x0186, 0x018C, 0x8189, 0x0198, 0x819D, 0x8197, 0x0192, + 0x01B0, 0x81B5, 0x81BF, 0x01BA, 0x81AB, 0x01AE, 0x01A4, 0x81A1, + 0x01E0, 0x81E5, 0x81EF, 0x01EA, 0x81FB, 0x01FE, 0x01F4, 0x81F1, + 0x81D3, 0x01D6, 0x01DC, 0x81D9, 0x01C8, 0x81CD, 0x81C7, 0x01C2, + 0x0140, 0x8145, 0x814F, 0x014A, 0x815B, 0x015E, 0x0154, 0x8151, + 0x8173, 0x0176, 0x017C, 0x8179, 0x0168, 0x816D, 0x8167, 0x0162, + 0x8123, 0x0126, 0x012C, 0x8129, 0x0138, 0x813D, 0x8137, 0x0132, + 0x0110, 0x8115, 0x811F, 0x011A, 0x810B, 0x010E, 0x0104, 0x8101, + 0x8303, 0x0306, 0x030C, 0x8309, 0x0318, 0x831D, 0x8317, 0x0312, + 0x0330, 0x8335, 0x833F, 0x033A, 0x832B, 0x032E, 0x0324, 0x8321, + 0x0360, 0x8365, 0x836F, 0x036A, 0x837B, 0x037E, 0x0374, 0x8371, + 0x8353, 0x0356, 0x035C, 0x8359, 0x0348, 0x834D, 0x8347, 0x0342, + 0x03C0, 0x83C5, 0x83CF, 0x03CA, 0x83DB, 0x03DE, 0x03D4, 0x83D1, + 0x83F3, 0x03F6, 0x03FC, 0x83F9, 0x03E8, 0x83ED, 0x83E7, 0x03E2, + 0x83A3, 0x03A6, 0x03AC, 0x83A9, 0x03B8, 0x83BD, 0x83B7, 0x03B2, + 0x0390, 0x8395, 0x839F, 0x039A, 0x838B, 0x038E, 0x0384, 0x8381, + 0x0280, 0x8285, 0x828F, 0x028A, 0x829B, 0x029E, 0x0294, 0x8291, + 0x82B3, 0x02B6, 0x02BC, 0x82B9, 0x02A8, 0x82AD, 0x82A7, 0x02A2, + 0x82E3, 0x02E6, 0x02EC, 0x82E9, 0x02F8, 0x82FD, 0x82F7, 0x02F2, + 0x02D0, 0x82D5, 0x82DF, 0x02DA, 0x82CB, 0x02CE, 0x02C4, 0x82C1, + 0x8243, 0x0246, 0x024C, 0x8249, 0x0258, 0x825D, 0x8257, 0x0252, + 0x0270, 0x8275, 0x827F, 0x027A, 0x826B, 0x026E, 0x0264, 0x8261, + 0x0220, 0x8225, 0x822F, 0x022A, 0x823B, 0x023E, 0x0234, 0x8231, + 0x8213, 0x0216, 0x021C, 0x8219, 0x0208, 0x820D, 0x8207, 0x0202 + }; + + + const char *tmpData= data; + const char *end = tmpData+len; + while(tmpData < end){ + crc = (crc << 8) ^ table[((crc >> 8) ^ *(char*)tmpData++) & 0x00ff]; + } + + return crc; +} + + inline unsigned int crc8(unsigned int crc, const char *data, size_t len){ + + // crc table for x8+ x2+ x1+ x0 polynomial + static const uint8_t crc_table[] = { + 0x00, 0x07, 0x0e, 0x09, 0x1c, 0x1b, 0x12, 0x15, 0x38, 0x3f, 0x36, 0x31, + 0x24, 0x23, 0x2a, 0x2d, 0x70, 0x77, 0x7e, 0x79, 0x6c, 0x6b, 0x62, 0x65, + 0x48, 0x4f, 0x46, 0x41, 0x54, 0x53, 0x5a, 0x5d, 0xe0, 0xe7, 0xee, 0xe9, + 0xfc, 0xfb, 0xf2, 0xf5, 0xd8, 0xdf, 0xd6, 0xd1, 0xc4, 0xc3, 0xca, 0xcd, + 0x90, 0x97, 0x9e, 0x99, 0x8c, 0x8b, 0x82, 0x85, 0xa8, 0xaf, 0xa6, 0xa1, + 0xb4, 0xb3, 0xba, 0xbd, 0xc7, 0xc0, 0xc9, 0xce, 0xdb, 0xdc, 0xd5, 0xd2, + 0xff, 0xf8, 0xf1, 0xf6, 0xe3, 0xe4, 0xed, 0xea, 0xb7, 0xb0, 0xb9, 0xbe, + 0xab, 0xac, 0xa5, 0xa2, 0x8f, 0x88, 0x81, 0x86, 0x93, 0x94, 0x9d, 0x9a, + 0x27, 0x20, 0x29, 0x2e, 0x3b, 0x3c, 0x35, 0x32, 0x1f, 0x18, 0x11, 0x16, + 0x03, 0x04, 0x0d, 0x0a, 0x57, 0x50, 0x59, 0x5e, 0x4b, 0x4c, 0x45, 0x42, + 0x6f, 0x68, 0x61, 0x66, 0x73, 0x74, 0x7d, 0x7a, 0x89, 0x8e, 0x87, 0x80, + 0x95, 0x92, 0x9b, 0x9c, 0xb1, 0xb6, 0xbf, 0xb8, 0xad, 0xaa, 0xa3, 0xa4, + 0xf9, 0xfe, 0xf7, 0xf0, 0xe5, 0xe2, 0xeb, 0xec, 0xc1, 0xc6, 0xcf, 0xc8, + 0xdd, 0xda, 0xd3, 0xd4, 0x69, 0x6e, 0x67, 0x60, 0x75, 0x72, 0x7b, 0x7c, + 0x51, 0x56, 0x5f, 0x58, 0x4d, 0x4a, 0x43, 0x44, 0x19, 0x1e, 0x17, 0x10, + 0x05, 0x02, 0x0b, 0x0c, 0x21, 0x26, 0x2f, 0x28, 0x3d, 0x3a, 0x33, 0x34, + 0x4e, 0x49, 0x40, 0x47, 0x52, 0x55, 0x5c, 0x5b, 0x76, 0x71, 0x78, 0x7f, + 0x6a, 0x6d, 0x64, 0x63, 0x3e, 0x39, 0x30, 0x37, 0x22, 0x25, 0x2c, 0x2b, + 0x06, 0x01, 0x08, 0x0f, 0x1a, 0x1d, 0x14, 0x13, 0xae, 0xa9, 0xa0, 0xa7, + 0xb2, 0xb5, 0xbc, 0xbb, 0x96, 0x91, 0x98, 0x9f, 0x8a, 0x8d, 0x84, 0x83, + 0xde, 0xd9, 0xd0, 0xd7, 0xc2, 0xc5, 0xcc, 0xcb, 0xe6, 0xe1, 0xe8, 0xef, + 0xfa, 0xfd, 0xf4, 0xf3 + }; + + const char *tmpData = data; + const char *end = tmpData + len; + while (tmpData < end){ + crc = crc_table[(*tmpData++) ^ (unsigned char)crc] ^ ((crc << 8) & 0xff); + } + return crc; +} + + + }// namespace checksum diff --git a/lib/dtsc.cpp b/lib/dtsc.cpp index c40ae698..826128d7 100644 --- a/lib/dtsc.cpp +++ b/lib/dtsc.cpp @@ -1851,9 +1851,13 @@ namespace DTSC{ setInit(trackIdx, init.data(), init.size()); } - /// Sets the given track's init data.setvod + /// Sets the given track's init data. void Meta::setInit(size_t trackIdx, const char *init, size_t initLen){ DTSC::Track &t = tracks.at(trackIdx); + if (initLen > t.trackInitField.size){ + FAIL_MSG("Attempting to store %zu bytes of init data, but we only have room for %" PRIu32 " bytes!", initLen, t.trackInitField.size); + initLen = t.trackInitField.size; + } char *_init = t.track.getPointer(t.trackInitField); Bit::htobs(_init, initLen); memcpy(_init + 2, init, initLen); diff --git a/lib/flac.cpp b/lib/flac.cpp new file mode 100644 index 00000000..befd75c0 --- /dev/null +++ b/lib/flac.cpp @@ -0,0 +1,128 @@ +#include "flac.h" + +/// Checks the first 4 bytes for the string "flaC". Implementing a basic FLAC header check, +/// returning true if it is, false if not. +bool FLAC::is_header(const char *header){ + if (header[0] != 'f') return false; + if (header[1] != 'L') return false; + if (header[2] != 'a') return false; + if (header[3] != 'C') return false; + return true; +}// FLAC::is_header + +size_t FLAC::utfBytes(char p){ + if ((p & 0x80) == 0x00){return 1;} + if ((p & 0xE0) == 0xC0){return 2;} + if ((p & 0xF0) == 0xE0){return 3;} + if ((p & 0xF8) == 0xF0){return 4;} + if ((p & 0xFC) == 0xF8){return 5;} + if ((p & 0xFE) == 0xFC){return 6;} + if ((p & 0xFF) == 0xFE){return 7;} + return 9; +} + +uint32_t FLAC::utfVal(char *p){ + size_t bytes = utfBytes(*p); + uint32_t ret = 0; + + if (bytes == 1){ + ret = (uint32_t)*p; + }else if (bytes == 2){ + ret = (uint32_t)(*p & 0x1F) << 6; + ret = ret | (*(p + 1) & 0x3f); + }else if (bytes == 3){ + ret = (uint32_t)(*p & 0x1F) << 6; + ret = (ret | (*(p + 1) & 0x3f)) << 6; + ret = ret | (*(p + 2) & 0x3f); + }else if (bytes == 4){ + ret = (uint32_t)(*p & 0x1F) << 6; + ret = (ret | (*(p + 1) & 0x3f)) << 6; + ret = (ret | (*(p + 2) & 0x3f)) << 6; + ret = ret | (*(p + 3) & 0x3f); + } + + return ret; +} + +FLAC::Frame::Frame(char *pkt){ + data = pkt; + if (data[0] != 0xFF || (data[1] & 0xFC) != 0xF8){ + WARN_MSG("Sync code incorrect! Ignoring FLAC frame"); + FAIL_MSG("%x %x", data[0], data[1]); + data = 0; + } +} + +uint16_t FLAC::Frame::samples(){ + if (!data){return 0;} + switch ((data[2] & 0xF0) >> 4){ + case 0: return 0; // reserved + case 1: return 192; + case 2: return 576; + case 3: return 1152; + case 4: return 2304; + case 5: return 4608; + case 6: return 1; // 1b at end + case 7: return 2; // 2b at end + default: return 256 << (((data[2] & 0xf0) >> 4) - 8); + } +} +uint32_t FLAC::Frame::rate(){ + if (!data){return 0;} + switch (data[2] & 0x0F){ + case 0: return 0; // get from STREAMINFO + case 1: return 88200; + case 2: return 176400; + case 3: return 192000; + case 4: return 8000; + case 5: return 16000; + case 6: return 22050; + case 7: return 24000; + case 8: return 32000; + case 9: return 44100; + case 10: return 48000; + case 11: return 96000; + case 12: return 1; // 1b at end, *1000 + case 13: return 2; // 2b at end + case 14: return 3; // 2b at end, *10 + case 15: return 0; // invalid, get from STREAMINFO + default: return 0; + } +} + +uint8_t FLAC::Frame::channels(){ + if (!data){return 0;} + uint8_t ret = ((data[3] & 0xF0) >> 4) + 1; + if (ret > 8 && ret < 12){return 2;}// special stereo + return ret; +} +uint8_t FLAC::Frame::size(){ + if (!data){return 0;} + switch (data[3] & 0x0E){ + case 0: return 0; // get from STREAMINFO + case 1: return 8; + case 2: return 12; + case 3: return 0; // reserved + case 4: return 16; + case 5: return 20; + case 6: return 24; + case 7: return 0; // reserved + default: return 0; + } +} + +uint32_t FLAC::Frame::utfVal(){ + return FLAC::utfVal(data + 4); +} + +std::string FLAC::Frame::toPrettyString(){ + if (!data){return "Invalid frame";} + std::stringstream r; + r << "FLAC frame" << std::endl; + r << " Block size: " << ((data[1] & 0x1) ? "variable" : "fixed") << std::endl; + r << " Samples: " << samples() << std::endl; + r << " Rate: " << rate() << "Hz" << std::endl; + r << " Channels: " << (int)channels() << std::endl; + r << " Size: " << (int)size() << "-bit" << std::endl; + return r.str(); +} diff --git a/lib/flac.h b/lib/flac.h new file mode 100644 index 00000000..bd339455 --- /dev/null +++ b/lib/flac.h @@ -0,0 +1,32 @@ +#pragma once +#include +#include +#include +#include +#include //for stat +#include + +namespace FLAC{ + bool is_header(const char *header); ///< Checks the first 4 bytes for the string "flaC". + size_t utfBytes(char p); // UTF encoding byte size + uint32_t utfVal(char *p); // UTF encoding value + + size_t rate(); + uint8_t channels(); + + class Frame{ + public: + Frame(char *pkt); + uint16_t samples(); + uint32_t rate(); + uint8_t channels(); + uint8_t size(); + + uint32_t utfVal(); // UTF encoding value + std::string toPrettyString(); + + private: + char *data; + }; + +}// namespace FLAC diff --git a/lib/meson.build b/lib/meson.build index b690c859..a9708ab6 100644 --- a/lib/meson.build +++ b/lib/meson.build @@ -57,6 +57,7 @@ headers = [ 'websocket.h', 'url.h', 'urireader.h', + 'flac.h', ] if have_srt @@ -123,6 +124,7 @@ libmist = library('mist', 'url.cpp', 'urireader.cpp', 'websocket.cpp', + 'flac.cpp', extra_code, include_directories: incroot, dependencies: mist_deps, diff --git a/src/analysers/analyser_flac.cpp b/src/analysers/analyser_flac.cpp new file mode 100644 index 00000000..92eb0e81 --- /dev/null +++ b/src/analysers/analyser_flac.cpp @@ -0,0 +1,183 @@ +/// \file flac_analyser.cpp +/// Contains the code for the FLAC Analysing tool. +#include +#include +#include + +#include "analyser_flac.h" +#include +#include +#include + +AnalyserFLAC::AnalyserFLAC(Util::Config &conf) : Analyser(conf){ + a = conf.getInteger("filter"); + headerParsed = false; + curPos = 0; + bufferSize = 0; + + ptr = (char *)flacBuffer.c_str(); + prev_header_size = 0; + pos = NULL; + forceFill = false; +} + +bool AnalyserFLAC::readMagicPacket(){ + char magic[4]; + if (fread(magic, 4, 1, stdin) != 1){ + std::cout << "Could not read magic word - aborting!" << std::endl; + return false; + } + if (FLAC::is_header(magic)){ + std::cout << "Found magic packet" << std::endl; + curPos = 4; + return true; + } + std::cout << "Not a FLAC file - aborting!" << std::endl; + return false; +} + +void AnalyserFLAC::init(Util::Config &conf){ + Analyser::init(conf); + JSON::Value opt; + opt["long"] = "filter"; + opt["short"] = "F"; + opt["arg"] = "num"; + opt["default"] = "0"; + opt["help"] = + "Only print information about this tag type (8 = audio, 9 = video, 18 = meta, 0 = all)"; + conf.addOption("filter", opt); + opt.null(); + + if (feof(stdin)){ + WARN_MSG("cannot open stdin"); + return; + } +} + +bool AnalyserFLAC::readMeta(){ + if (!readMagicPacket()){return false;} + + bool lastMeta = false; + char metahead[4]; + while (!feof(stdin) && !lastMeta){ + if (fread(metahead, 4, 1, stdin) != 1){ + std::cout << "Could not read metadata block header - aborting!" << std::endl; + return 1; + } + curPos += 4; + + lastMeta = (metahead[0] & 0x80); // check for last metadata block flag + std::string mType; + switch (metahead[0] & 0x7F){ + case 0: mType = "STREAMINFO"; break; + case 1: mType = "PADDING"; break; + case 2: mType = "APPLICATION"; break; + case 3: mType = "SEEKTABLE"; break; + case 4: mType = "VORBIS_COMMENT"; break; + case 5: mType = "CUESHEET"; break; + case 6: mType = "PICTURE"; break; + case 127: mType = "INVALID"; break; + default: mType = "UNKNOWN"; break; + } + unsigned int bytes = Bit::btoh24(metahead + 1); + curPos += bytes; + fseek(stdin, bytes, SEEK_CUR); + std::cout << "Found metadata block of type " << mType << ", skipping " << bytes << " bytes" << std::endl; + if (mType == "STREAMINFO"){FAIL_MSG("streaminfo");} + } + INFO_MSG("last metadata"); + headerParsed = true; + return true; +} + +bool AnalyserFLAC::parsePacket(){ + if (feof(stdin) && flacBuffer.size() < 100){ + stop(); + return false; + } + + if (!headerParsed){ + if (!readMeta()){ + stop(); + return false; + } + } + uint64_t needed = 40000; + + // fill up buffer as we go + if (flacBuffer.length() < 8100 || forceFill){ + forceFill = false; + + for (int i = 0; i < needed; i++){ + char tmp = std::cin.get(); + bufferSize++; + flacBuffer += tmp; + + if (!std::cin.good()){ + WARN_MSG("End, process remaining buffer data: %zu bytes", flacBuffer.size()); + + if (flacBuffer.size() < 1){ + FAIL_MSG("eof"); + return false; + } + + break; + } + } + + start = &flacBuffer[0]; + end = &flacBuffer[flacBuffer.size()]; + } + + if (!pos){pos = start + 1;} + + while (pos < end - 5){ + uint16_t tmp = (*pos << 8) | *(pos + 1); + + if (tmp == 0xfff8){// check sync code + char *a = pos; + char u = *(a + 4); + + int utfv = FLAC::utfVal(start + 4); // read framenumber + + if (utfv + 1 != FLAC::utfVal(a + 4)){ + FAIL_MSG("framenr: %d, end framenr: %d, size: %zu curPos: %" PRIu64, FLAC::utfVal(start + 4), + FLAC::utfVal(a + 4), (size_t)(pos - start), curPos); + }else{ + INFO_MSG("framenr: %d, end framenr: %d, size: %zu curPos: %" PRIu64, FLAC::utfVal(start + 4), + FLAC::utfVal(a + 4), (size_t)(pos - start), curPos); + } + + int skip = FLAC::utfBytes(u); + + prev_header_size = skip + 4; + if (checksum::crc8(0, pos, prev_header_size) == *(a + prev_header_size)){ + // checksum pass, valid startframe found + if (((utfv + 1 != FLAC::utfVal(a + 4))) && (utfv != 0)){ + WARN_MSG("error frame, found: %d, expected: %d, ignore.. ", FLAC::utfVal(a + 4), utfv + 1); + }else{ + + FLAC::Frame f(start); + curPos += (pos - start); + flacBuffer.erase(0, pos - start); + + start = &flacBuffer[0]; + end = &flacBuffer[flacBuffer.size()]; + pos = start + 2; + return true; + } + }else{ + WARN_MSG("Checksum mismatch! %x - %x, curPos: %" PRIu64, *(a + prev_header_size), + checksum::crc8(0, pos, prev_header_size), curPos + (pos - start)); + } + } + pos++; + } + + if (std::cin.good()){ + forceFill = true; + return true; + } + + return false; +} diff --git a/src/analysers/analyser_flac.h b/src/analysers/analyser_flac.h new file mode 100644 index 00000000..7cdda4f1 --- /dev/null +++ b/src/analysers/analyser_flac.h @@ -0,0 +1,34 @@ +#pragma once +#include "analyser.h" + +class AnalyserFLAC : public Analyser{ + +public: + AnalyserFLAC(Util::Config &conf); + bool parsePacket(); + static void init(Util::Config &conf); + bool readMagicPacket(); + bool readFrame(); + bool readMeta(); + +private: + int64_t a; + uint64_t neededBytes(); + void newFrame(char *data); + bool headerParsed; + std::string flacBuffer; + uint64_t bufferSize; + uint64_t curPos; + size_t utfBytes(char p); + + bool forceFill; + + char *ptr; + bool stopProcessing; + + char *start; // = &flacBuffer[0]; + char *end; // = &flacBuffer[flacBuffer.size()]; + char *pos; // = start; + int prev_header_size; + FILE *inFile; +}; diff --git a/src/analysers/meson.build b/src/analysers/meson.build index 97b4cdf3..eb58bd6c 100644 --- a/src/analysers/meson.build +++ b/src/analysers/meson.build @@ -11,6 +11,7 @@ analysers = [ {'name': 'HLS', 'format': 'hls'}, {'name': 'RIFF', 'format': 'riff'}, {'name': 'RTSP', 'format': 'rtsp'}, + {'name': 'FLAC', 'format': 'flac'}, ] foreach analyser : analysers diff --git a/src/input/input_ebml.cpp b/src/input/input_ebml.cpp index 5ec61810..5b854495 100644 --- a/src/input/input_ebml.cpp +++ b/src/input/input_ebml.cpp @@ -37,6 +37,7 @@ namespace Mist{ capa["codecs"]["audio"].append("AC3"); capa["codecs"]["audio"].append("FLOAT"); capa["codecs"]["audio"].append("DTS"); + capa["codecs"]["audio"].append("FLAC"); capa["codecs"]["metadata"].append("JSON"); capa["codecs"]["subtitle"].append("subtitle"); lastClusterBPos = 0; @@ -293,6 +294,12 @@ namespace Mist{ trueCodec = "AC3"; trueType = "audio"; } + if (codec == "A_FLAC"){ + trueCodec = "FLAC"; + trueType = "audio"; + tmpElem = E.findChild(EBML::EID_CODECPRIVATE); + if (tmpElem){init = tmpElem.getValStringUntrimmed();} + } if (codec == "A_MPEG/L3"){ trueCodec = "MP3"; trueType = "audio"; diff --git a/src/input/input_flac.cpp b/src/input/input_flac.cpp new file mode 100644 index 00000000..38ca3a89 --- /dev/null +++ b/src/input/input_flac.cpp @@ -0,0 +1,347 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include //for stat +#include //for stat +#include //for stat + +#include "input_flac.h" +#include + +namespace Mist{ + inputFLAC::inputFLAC(Util::Config *cfg) : Input(cfg){ + capa["name"] = "FLAC"; + capa["desc"] = "Allows loading FLAC files for Audio on Demand."; + capa["source_match"] = "/*.flac"; + capa["source_file"] = "$source"; + capa["priority"] = 9; + capa["codecs"]["audio"].append("FLAC"); + stopProcessing = false; + stopFilling = false; + pos = 2; + curPos = 0; + sampleNr = 0; + frameNr = 0; + sampleRate = 0; + blockSize = 0; + bitRate = 0; + channels = 0; + tNum = INVALID_TRACK_ID; + } + + inputFLAC::~inputFLAC(){} + + bool inputFLAC::checkArguments(){ + if (config->getString("input") == "-"){ + std::cerr << "Input from stdin not yet supported" << std::endl; + return false; + } + if (!config->getString("streamname").size()){ + if (config->getString("output") == "-"){ + std::cerr << "Output to stdout not yet supported" << std::endl; + return false; + } + }else{ + if (config->getString("output") != "-"){ + std::cerr << "File output in player mode not supported" << std::endl; + return false; + } + } + return true; + } + + bool inputFLAC::preRun(){ + inFile = fopen(config->getString("input").c_str(), "r"); + if (!inFile){return false;} + return true; + } + + void inputFLAC::stripID3tag(){ + char header[10]; + fread(header, 10, 1, inFile); // Read a 10 byte header + if (header[0] == 'I' || header[1] == 'D' || header[2] == '3'){ + uint64_t id3size = (((int)header[6] & 0x7F) << 21) | (((int)header[7] & 0x7F) << 14) | + (((int)header[8] & 0x7F) << 7) | + ((header[9] & 0x7F) + 10 + ((header[5] & 0x10) ? 10 : 0)); + INFO_MSG("strip ID3 Tag, size: %" PRIu64 " bytes", id3size); + curPos += id3size; + fseek(inFile, id3size, SEEK_SET); + }else{ + fseek(inFile, 0, SEEK_SET); + } + } + + bool inputFLAC::readMagicPacket(){ + char magic[4]; + if (fread(magic, 4, 1, inFile) != 1){ + FAIL_MSG("Could not read magic word - aborting!"); + return false; + } + + if (FLAC::is_header(magic)){ + curPos += 4; + return true; + } + + FAIL_MSG("Not a FLAC file - aborting!"); + return false; + } + + bool inputFLAC::readHeader(){ + if (!inFile){return false;} + + if (readExistingHeader()){ + WARN_MSG("header exists, read old one"); + + if (M.inputLocalVars.isMember("blockSize")){ + return true; + }else{ + INFO_MSG("Header needs update as it contains no blockSize, regenerating"); + } + } + + meta.reInit(config->getString("streamname")); + Util::ResizeablePointer tmpInit; + tmpInit.append("fLaC", 4); + + stripID3tag(); + if (!readMagicPacket()){return false;} + + bool lastMeta = false; + char metahead[4]; + while (!feof(inFile) && !lastMeta){ + if (fread(metahead, 4, 1, inFile) != 1){ + FAIL_MSG("Could not read metadata block header - aborting!"); + return false; + } + + uint32_t bytes = Bit::btoh24(metahead + 1); + lastMeta = (metahead[0] & 0x80); // check for last metadata block flag + + std::string mType; + switch (metahead[0] & 0x7F){ + case 0:{ + mType = "STREAMINFO"; + + char metaTmp[bytes]; + if (fread(metaTmp, bytes, 1, inFile) != 1){ + FAIL_MSG("Could not read streaminfo metadata - aborting!"); + return false; + } + + // Store this block in init data + metahead[0] |= 0x80; //Set last metadata block flag + tmpInit.append(metahead, 4); + tmpInit.append(metaTmp, bytes); + + + HIGH_MSG("min blocksize: %zu, max: %zu", (size_t)(metaTmp[0] << 8 | metaTmp[1]), + (size_t)(metaTmp[2] << 8) | metaTmp[3]); + + blockSize = (metaTmp[0] << 8 | metaTmp[1]); + if ((metaTmp[2] << 8 | metaTmp[3]) != blockSize){ + FAIL_MSG("variable block size not supported!"); + return 1; + } + + sampleRate = ((metaTmp[10] << 12) | (metaTmp[11] << 4) | ((metaTmp[12] & 0xf0) >> 4)); + WARN_MSG("setting samplerate: %zu", sampleRate); + + channels = ((metaTmp[12] & 0xe) >> 1) + 1; + HIGH_MSG("setting channels: %zu", channels); + + break; + } + case 1: mType = "PADDING"; break; + case 2: mType = "APPLICATION"; break; + case 3: mType = "SEEKTABLE"; break; + case 4: mType = "VORBIS_COMMENT"; break; + case 5: mType = "CUESHEET"; break; + case 6: mType = "PICTURE"; break; + case 127: mType = "INVALID"; break; + default: mType = "UNKNOWN"; break; + } + curPos += 4 + bytes; + if (mType != "STREAMINFO"){fseek(inFile, bytes, SEEK_CUR);} + INFO_MSG("Found %" PRIu32 "b metadata block of type %s", bytes, mType.c_str()); + } + + if (!sampleRate){ + FAIL_MSG("Could not get sample rate from file header"); + return false; + } + if (!channels){ + FAIL_MSG("no channel information found!"); + return false; + } + + + tNum = meta.addTrack(); + meta.setID(tNum, tNum); + meta.setType(tNum, "audio"); + meta.setCodec(tNum, "FLAC"); + meta.setRate(tNum, sampleRate); + meta.setChannels(tNum, channels); + meta.setInit(tNum, tmpInit, tmpInit.size()); + meta.inputLocalVars["blockSize"] = blockSize; + + getNext(); + while (thisPacket){ + meta.update(thisPacket); + getNext(); + } + return true; + } + + bool inputFLAC::fillBuffer(size_t size){ + if (feof(inFile)){ + INFO_MSG("EOF"); + return flacBuffer.size(); + } + + for (int i = 0; i < size; i++){ + char tmp = fgetc(inFile); + if (!feof(inFile)){ + flacBuffer += tmp; + }else{ + WARN_MSG("End, process remaining buffer data: %zu bytes", flacBuffer.size()); + stopFilling = true; + break; + } + } + + start = (char *)flacBuffer.data(); + end = start + flacBuffer.size(); + + return flacBuffer.size(); + } + + void inputFLAC::getNext(size_t idx){ + while (!stopProcessing){ + blockSize = M.inputLocalVars["blockSize"].asInt(); + + // fill buffer if needed + if (pos + 16 >= flacBuffer.size()){ + if (!stopFilling){ + if (!fillBuffer(1000)){ + thisPacket.null(); + return; + } + }else{ + if (!flacBuffer.size()){ + thisPacket.null(); + return; + } + } + } + + if (stopFilling && ((flacBuffer.size() - pos) < 1)){ + uint64_t timestamp = (sampleNr * 1000) / sampleRate; + HIGH_MSG("process last frame size: %zu", flacBuffer.size()); + thisTime = timestamp; + thisIdx = tNum; + thisPacket.genericFill(timestamp, 0, 0, start, flacBuffer.size(), curPos, false); + flacBuffer.clear(); + stopProcessing = true; + return; + } + + uint16_t tmp = Bit::btohs(start + pos); + if (tmp == 0xfff8){// check sync code + char *a = start + pos; + + int utfv = FLAC::utfVal(start + 4); // read framenumber + int skip = FLAC::utfBytes(*(a + 4)); + prev_header_size = skip + 4; + + if (checksum::crc8(0, start + pos, prev_header_size) == *(a + prev_header_size)){ + // checksum pass, valid frame found + if (((utfv + 1 != FLAC::utfVal(a + 4))) && + (utfv != 0)){// TODO: this check works only if framenumbers are used in the header + HIGH_MSG("error frame, found: %d, expected: %d, ignore.. ", FLAC::utfVal(a + 4), utfv + 1); + // checksum pass, but incorrect framenr... happens sometimes + }else{ + // FLAC_Frame f(start); + FLAC::Frame f(start); + if (sampleRate != 0 && f.rate() != sampleRate){ + FAIL_MSG("samplerate from frame header: %d, sampleRate from file header: %zu", f.rate(), sampleRate); + thisPacket.null(); + return; + } + + frameNr++; + uint64_t timestamp = (sampleNr * 1000) / sampleRate; + if (blockSize != f.samples()){ + FAIL_MSG("blockSize differs, %zu %u", blockSize, f.samples()); + } + + if (blockSize == 0){ + FAIL_MSG("Cannot detect block size"); + return; + } + if (blockSize == 1 || blockSize == 2){ + // get blockSize from end of header + // TODO: need to test + FAIL_MSG("weird blocksize"); + blockSize = *(start + prev_header_size); // 8 or 16 bits value + } + + sampleNr += blockSize; + + thisTime = timestamp; + thisIdx = tNum; + thisPacket.genericFill(timestamp, 0, 0, start, pos, curPos, false); + + curPos += pos; + flacBuffer.erase(0, pos); + + start = (char *)flacBuffer.data(); + end = start + flacBuffer.size(); + pos = 2; + + return; + } + } + } + pos++; + } + + thisPacket.null(); + return; + } + + void inputFLAC::seek(uint64_t seekTime, size_t idx){ + uint64_t mainTrack = M.mainTrack(); + blockSize = M.inputLocalVars["blockSize"].asInt(); + sampleRate = meta.getRate(mainTrack); + + pos = 2; + clearerr(inFile); + flacBuffer.clear(); + stopProcessing = false; + stopFilling = false; + DTSC::Keys keys(M.keys(mainTrack)); + DTSC::Parts parts(M.parts(mainTrack)); + uint64_t seekPos = keys.getBpos(0); + uint64_t seekKeyTime = keys.getTime(0); + // Replay the parts of the previous keyframe, so the timestaps match up + for (size_t i = 0; i < keys.getEndValid(); i++){ + if (keys.getTime(i) > seekTime){break;} + DONTEVEN_MSG("Seeking to %" PRIu64 ", found %" PRIu64 "...", seekTime, keys.getTime(i)); + seekPos = keys.getBpos(i); + seekKeyTime = keys.getTime(i); + } + Util::fseek(inFile, seekPos, SEEK_SET); + sampleNr = (uint64_t)((seekKeyTime * sampleRate / 1000 + (blockSize / 2)) / blockSize) * blockSize; + HIGH_MSG("seek: %" PRIu64 ", sampleNr: %" PRIu64 ", time: %" PRIu64, seekPos, sampleNr, seekKeyTime); + curPos = seekPos; + return; + } +}// namespace Mist diff --git a/src/input/input_flac.h b/src/input/input_flac.h new file mode 100644 index 00000000..155a1f0f --- /dev/null +++ b/src/input/input_flac.h @@ -0,0 +1,48 @@ +#include "input.h" +#include +#include + +namespace Mist{ + class inputFLAC : public Input{ + public: + inputFLAC(Util::Config *cfg); + ~inputFLAC(); + + protected: + bool checkArguments(); + bool preRun(); + bool readHeader(); + void getNext(size_t idx = INVALID_TRACK_ID); + void seek(uint64_t seekTime, size_t idx = INVALID_TRACK_ID); + FILE *inFile; + + private: + void stripID3tag(); + bool readMagicPacket(); + bool fillBuffer(size_t size = 40960); + uint64_t neededBytes(); + char *ptr; + std::string flacBuffer; + Socket::Buffer buffer; + uint64_t bufferSize; + uint64_t curPos; + + bool stopProcessing; + bool stopFilling; + size_t tNum; + size_t pos; + char *end; + char *start; + int prev_header_size; + uint64_t sampleNr; + uint64_t frameNr; + + size_t sampleRate; + size_t blockSize; + size_t bitRate; + size_t channels; + }; + +}// namespace Mist + +typedef Mist::inputFLAC mistIn; diff --git a/src/input/meson.build b/src/input/meson.build index 437e47bc..37524c44 100644 --- a/src/input/meson.build +++ b/src/input/meson.build @@ -17,6 +17,7 @@ inputs = [ {'name' : 'SRT', 'format' : 'srt'}, {'name' : 'SDP', 'format' : 'sdp'}, {'name' : 'AAC', 'format' : 'aac'}, + {'name' : 'FLAC', 'format' : 'flac'}, ] #Referenced by process targets diff --git a/src/output/meson.build b/src/output/meson.build index f9f137bc..940cc289 100644 --- a/src/output/meson.build +++ b/src/output/meson.build @@ -6,6 +6,7 @@ outputs = [ {'name' : 'HTTPMinimalServer', 'format' : 'http_minimalserver', 'extra': ['http']}, {'name' : 'MP4', 'format' : 'mp4', 'extra': ['http']}, {'name' : 'AAC', 'format' : 'aac', 'extra': ['http']}, + {'name' : 'FLAC', 'format' : 'flac', 'extra': ['http']}, {'name' : 'MP3', 'format' : 'mp3', 'extra': ['http']}, {'name' : 'H264', 'format' : 'h264', 'extra': ['http']}, {'name' : 'HDS', 'format' : 'hds', 'extra': ['http']}, diff --git a/src/output/output_ebml.cpp b/src/output/output_ebml.cpp index 9ac330ca..f32473f0 100644 --- a/src/output/output_ebml.cpp +++ b/src/output/output_ebml.cpp @@ -67,6 +67,7 @@ namespace Mist{ capa["codecs"][0u][0u].append("MPEG2"); capa["codecs"][0u][0u].append("AV1"); capa["codecs"][0u][1u].append("AAC"); + capa["codecs"][0u][1u].append("FLAC"); capa["codecs"][0u][1u].append("vorbis"); capa["codecs"][0u][1u].append("opus"); capa["codecs"][0u][1u].append("PCM"); @@ -194,6 +195,7 @@ namespace Mist{ if (codec == "PCM"){return "A_PCM/INT/BIG";} if (codec == "MP2"){return "A_MPEG/L2";} if (codec == "MP3"){return "A_MPEG/L3";} + if (codec == "FLAC"){return "A_FLAC";} if (codec == "AC3"){return "A_AC3";} if (codec == "ALAW"){return "A_MS/ACM";} if (codec == "ULAW"){return "A_MS/ACM";} diff --git a/src/output/output_flac.cpp b/src/output/output_flac.cpp new file mode 100644 index 00000000..5fe00b66 --- /dev/null +++ b/src/output/output_flac.cpp @@ -0,0 +1,50 @@ +#include "output_flac.h" + +namespace Mist{ + + OutFLAC::OutFLAC(Socket::Connection &conn) : HTTPOutput(conn){} + + void OutFLAC::init(Util::Config *cfg){ + HTTPOutput::init(cfg); + capa["name"] = "FLAC"; + capa["friendly"] = "Free Lossless Audio Codec"; + capa["desc"] = "Pseudostreaming in FLAC format over HTTP"; + capa["url_rel"] = "/$.flac"; + capa["url_match"] = "/$.flac"; + capa["codecs"][0u][0u].append("FLAC"); + capa["methods"][0u]["handler"] = "http"; + capa["methods"][0u]["type"] = "html5/audio/flac"; + capa["methods"][0u]["hrn"] = "FLAC progressive"; + capa["methods"][0u]["priority"] = 8; + + JSON::Value opt; + opt["arg"] = "string"; + opt["default"] = ""; + opt["arg_num"] = 1; + opt["help"] = "Target filename to store FLAC file as, or - for stdout."; + cfg->addOption("target", opt); + } + + void OutFLAC::sendNext(){ + char *dataPointer = 0; + size_t len = 0; + thisPacket.getString("data", dataPointer, len); + myConn.SendNow(dataPointer, len); + } + + void OutFLAC::sendHeader(){ + myConn.SendNow(M.getInit(M.mainTrack())); + sentHeader = true; + } + + void OutFLAC::respondHTTP(const HTTP::Parser &req, bool headersOnly){ + // Set global defaults + HTTPOutput::respondHTTP(req, headersOnly); + + H.StartResponse("200", "OK", req, myConn); + if (headersOnly){return;} + parseData = true; + wantRequest = false; + } + +}// namespace Mist diff --git a/src/output/output_flac.h b/src/output/output_flac.h new file mode 100644 index 00000000..04123188 --- /dev/null +++ b/src/output/output_flac.h @@ -0,0 +1,17 @@ +#include "output_http.h" + +namespace Mist{ + class OutFLAC : public HTTPOutput{ + public: + OutFLAC(Socket::Connection &conn); + static void init(Util::Config *cfg); + virtual void respondHTTP(const HTTP::Parser &req, bool headersOnly); + void sendNext(); + void sendHeader(); + + private: + bool isFileTarget(){return isRecording();} + }; +}// namespace Mist + +typedef Mist::OutFLAC mistOut;