FLAC support:
- FLAC container input and output support - FLAC container analyser - FLAC codec support in EBML input and output
This commit is contained in:
parent
0f692233e8
commit
a7183aedc5
17 changed files with 948 additions and 1 deletions
|
@ -386,6 +386,7 @@ makeAnalyser(H264 h264) #LTS
|
||||||
makeAnalyser(HLS hls) #LTS
|
makeAnalyser(HLS hls) #LTS
|
||||||
makeAnalyser(RIFF riff) #LTS
|
makeAnalyser(RIFF riff) #LTS
|
||||||
makeAnalyser(RTSP rtsp) #LTS
|
makeAnalyser(RTSP rtsp) #LTS
|
||||||
|
makeAnalyser(FLAC flac)
|
||||||
|
|
||||||
#LTS_START
|
#LTS_START
|
||||||
########################################
|
########################################
|
||||||
|
@ -462,6 +463,8 @@ makeInput(HLS hls)
|
||||||
makeInput(DTSC dtsc)
|
makeInput(DTSC dtsc)
|
||||||
makeInput(MP3 mp3)
|
makeInput(MP3 mp3)
|
||||||
makeInput(FLV flv)
|
makeInput(FLV flv)
|
||||||
|
makeInput(FLAC flac)
|
||||||
|
|
||||||
option(WITH_AV "Build a generic libav-based input (not distributable!)")
|
option(WITH_AV "Build a generic libav-based input (not distributable!)")
|
||||||
if (WITH_AV)
|
if (WITH_AV)
|
||||||
makeInput(AV av)
|
makeInput(AV av)
|
||||||
|
@ -574,6 +577,7 @@ makeOutput(EBML ebml)
|
||||||
makeOutput(RTSP rtsp)#LTS
|
makeOutput(RTSP rtsp)#LTS
|
||||||
makeOutput(WAV wav)#LTS
|
makeOutput(WAV wav)#LTS
|
||||||
makeOutput(SDP sdp http)
|
makeOutput(SDP sdp http)
|
||||||
|
makeOutput(FLAC flac http)
|
||||||
|
|
||||||
add_executable(MistSession
|
add_executable(MistSession
|
||||||
${BINARY_DIR}/mist/.headers
|
${BINARY_DIR}/mist/.headers
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
#include <stddef.h>
|
#include <stddef.h>
|
||||||
|
#include "defines.h"
|
||||||
|
|
||||||
namespace checksum{
|
namespace checksum{
|
||||||
inline unsigned int crc32c(unsigned int crc, const char *data, size_t len){
|
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);}
|
while (tmpData < end){crc = table[((unsigned char)crc) ^ *tmpData++] ^ (crc >> 8);}
|
||||||
return crc;
|
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
|
}// namespace checksum
|
||||||
|
|
|
@ -1851,9 +1851,13 @@ namespace DTSC{
|
||||||
setInit(trackIdx, init.data(), init.size());
|
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){
|
void Meta::setInit(size_t trackIdx, const char *init, size_t initLen){
|
||||||
DTSC::Track &t = tracks.at(trackIdx);
|
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);
|
char *_init = t.track.getPointer(t.trackInitField);
|
||||||
Bit::htobs(_init, initLen);
|
Bit::htobs(_init, initLen);
|
||||||
memcpy(_init + 2, init, initLen);
|
memcpy(_init + 2, init, initLen);
|
||||||
|
|
128
lib/flac.cpp
Normal file
128
lib/flac.cpp
Normal file
|
@ -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();
|
||||||
|
}
|
32
lib/flac.h
Normal file
32
lib/flac.h
Normal file
|
@ -0,0 +1,32 @@
|
||||||
|
#pragma once
|
||||||
|
#include <mist/defines.h>
|
||||||
|
#include <ostream>
|
||||||
|
#include <sstream>
|
||||||
|
#include <string>
|
||||||
|
#include <unistd.h> //for stat
|
||||||
|
#include <util.h>
|
||||||
|
|
||||||
|
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
|
|
@ -57,6 +57,7 @@ headers = [
|
||||||
'websocket.h',
|
'websocket.h',
|
||||||
'url.h',
|
'url.h',
|
||||||
'urireader.h',
|
'urireader.h',
|
||||||
|
'flac.h',
|
||||||
]
|
]
|
||||||
|
|
||||||
if have_srt
|
if have_srt
|
||||||
|
@ -123,6 +124,7 @@ libmist = library('mist',
|
||||||
'url.cpp',
|
'url.cpp',
|
||||||
'urireader.cpp',
|
'urireader.cpp',
|
||||||
'websocket.cpp',
|
'websocket.cpp',
|
||||||
|
'flac.cpp',
|
||||||
extra_code,
|
extra_code,
|
||||||
include_directories: incroot,
|
include_directories: incroot,
|
||||||
dependencies: mist_deps,
|
dependencies: mist_deps,
|
||||||
|
|
183
src/analysers/analyser_flac.cpp
Normal file
183
src/analysers/analyser_flac.cpp
Normal file
|
@ -0,0 +1,183 @@
|
||||||
|
/// \file flac_analyser.cpp
|
||||||
|
/// Contains the code for the FLAC Analysing tool.
|
||||||
|
#include <iostream>
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include "analyser_flac.h"
|
||||||
|
#include <mist/bitfields.h>
|
||||||
|
#include <mist/checksum.h>
|
||||||
|
#include <mist/flac.h>
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
34
src/analysers/analyser_flac.h
Normal file
34
src/analysers/analyser_flac.h
Normal file
|
@ -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;
|
||||||
|
};
|
|
@ -11,6 +11,7 @@ analysers = [
|
||||||
{'name': 'HLS', 'format': 'hls'},
|
{'name': 'HLS', 'format': 'hls'},
|
||||||
{'name': 'RIFF', 'format': 'riff'},
|
{'name': 'RIFF', 'format': 'riff'},
|
||||||
{'name': 'RTSP', 'format': 'rtsp'},
|
{'name': 'RTSP', 'format': 'rtsp'},
|
||||||
|
{'name': 'FLAC', 'format': 'flac'},
|
||||||
]
|
]
|
||||||
|
|
||||||
foreach analyser : analysers
|
foreach analyser : analysers
|
||||||
|
|
|
@ -37,6 +37,7 @@ namespace Mist{
|
||||||
capa["codecs"]["audio"].append("AC3");
|
capa["codecs"]["audio"].append("AC3");
|
||||||
capa["codecs"]["audio"].append("FLOAT");
|
capa["codecs"]["audio"].append("FLOAT");
|
||||||
capa["codecs"]["audio"].append("DTS");
|
capa["codecs"]["audio"].append("DTS");
|
||||||
|
capa["codecs"]["audio"].append("FLAC");
|
||||||
capa["codecs"]["metadata"].append("JSON");
|
capa["codecs"]["metadata"].append("JSON");
|
||||||
capa["codecs"]["subtitle"].append("subtitle");
|
capa["codecs"]["subtitle"].append("subtitle");
|
||||||
lastClusterBPos = 0;
|
lastClusterBPos = 0;
|
||||||
|
@ -293,6 +294,12 @@ namespace Mist{
|
||||||
trueCodec = "AC3";
|
trueCodec = "AC3";
|
||||||
trueType = "audio";
|
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"){
|
if (codec == "A_MPEG/L3"){
|
||||||
trueCodec = "MP3";
|
trueCodec = "MP3";
|
||||||
trueType = "audio";
|
trueType = "audio";
|
||||||
|
|
347
src/input/input_flac.cpp
Normal file
347
src/input/input_flac.cpp
Normal file
|
@ -0,0 +1,347 @@
|
||||||
|
#include <cerrno>
|
||||||
|
#include <cstdio>
|
||||||
|
#include <cstdlib>
|
||||||
|
#include <cstring>
|
||||||
|
#include <fstream>
|
||||||
|
#include <iostream>
|
||||||
|
#include <mist/defines.h>
|
||||||
|
#include <mist/stream.h>
|
||||||
|
#include <mist/util.h>
|
||||||
|
#include <string>
|
||||||
|
#include <sys/stat.h> //for stat
|
||||||
|
#include <sys/types.h> //for stat
|
||||||
|
#include <unistd.h> //for stat
|
||||||
|
|
||||||
|
#include "input_flac.h"
|
||||||
|
#include <mist/flac.h>
|
||||||
|
|
||||||
|
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
|
48
src/input/input_flac.h
Normal file
48
src/input/input_flac.h
Normal file
|
@ -0,0 +1,48 @@
|
||||||
|
#include "input.h"
|
||||||
|
#include <mist/checksum.h>
|
||||||
|
#include <mist/dtsc.h>
|
||||||
|
|
||||||
|
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;
|
|
@ -17,6 +17,7 @@ inputs = [
|
||||||
{'name' : 'SRT', 'format' : 'srt'},
|
{'name' : 'SRT', 'format' : 'srt'},
|
||||||
{'name' : 'SDP', 'format' : 'sdp'},
|
{'name' : 'SDP', 'format' : 'sdp'},
|
||||||
{'name' : 'AAC', 'format' : 'aac'},
|
{'name' : 'AAC', 'format' : 'aac'},
|
||||||
|
{'name' : 'FLAC', 'format' : 'flac'},
|
||||||
]
|
]
|
||||||
|
|
||||||
#Referenced by process targets
|
#Referenced by process targets
|
||||||
|
|
|
@ -6,6 +6,7 @@ outputs = [
|
||||||
{'name' : 'HTTPMinimalServer', 'format' : 'http_minimalserver', 'extra': ['http']},
|
{'name' : 'HTTPMinimalServer', 'format' : 'http_minimalserver', 'extra': ['http']},
|
||||||
{'name' : 'MP4', 'format' : 'mp4', 'extra': ['http']},
|
{'name' : 'MP4', 'format' : 'mp4', 'extra': ['http']},
|
||||||
{'name' : 'AAC', 'format' : 'aac', 'extra': ['http']},
|
{'name' : 'AAC', 'format' : 'aac', 'extra': ['http']},
|
||||||
|
{'name' : 'FLAC', 'format' : 'flac', 'extra': ['http']},
|
||||||
{'name' : 'MP3', 'format' : 'mp3', 'extra': ['http']},
|
{'name' : 'MP3', 'format' : 'mp3', 'extra': ['http']},
|
||||||
{'name' : 'H264', 'format' : 'h264', 'extra': ['http']},
|
{'name' : 'H264', 'format' : 'h264', 'extra': ['http']},
|
||||||
{'name' : 'HDS', 'format' : 'hds', 'extra': ['http']},
|
{'name' : 'HDS', 'format' : 'hds', 'extra': ['http']},
|
||||||
|
|
|
@ -67,6 +67,7 @@ namespace Mist{
|
||||||
capa["codecs"][0u][0u].append("MPEG2");
|
capa["codecs"][0u][0u].append("MPEG2");
|
||||||
capa["codecs"][0u][0u].append("AV1");
|
capa["codecs"][0u][0u].append("AV1");
|
||||||
capa["codecs"][0u][1u].append("AAC");
|
capa["codecs"][0u][1u].append("AAC");
|
||||||
|
capa["codecs"][0u][1u].append("FLAC");
|
||||||
capa["codecs"][0u][1u].append("vorbis");
|
capa["codecs"][0u][1u].append("vorbis");
|
||||||
capa["codecs"][0u][1u].append("opus");
|
capa["codecs"][0u][1u].append("opus");
|
||||||
capa["codecs"][0u][1u].append("PCM");
|
capa["codecs"][0u][1u].append("PCM");
|
||||||
|
@ -194,6 +195,7 @@ namespace Mist{
|
||||||
if (codec == "PCM"){return "A_PCM/INT/BIG";}
|
if (codec == "PCM"){return "A_PCM/INT/BIG";}
|
||||||
if (codec == "MP2"){return "A_MPEG/L2";}
|
if (codec == "MP2"){return "A_MPEG/L2";}
|
||||||
if (codec == "MP3"){return "A_MPEG/L3";}
|
if (codec == "MP3"){return "A_MPEG/L3";}
|
||||||
|
if (codec == "FLAC"){return "A_FLAC";}
|
||||||
if (codec == "AC3"){return "A_AC3";}
|
if (codec == "AC3"){return "A_AC3";}
|
||||||
if (codec == "ALAW"){return "A_MS/ACM";}
|
if (codec == "ALAW"){return "A_MS/ACM";}
|
||||||
if (codec == "ULAW"){return "A_MS/ACM";}
|
if (codec == "ULAW"){return "A_MS/ACM";}
|
||||||
|
|
50
src/output/output_flac.cpp
Normal file
50
src/output/output_flac.cpp
Normal file
|
@ -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
|
17
src/output/output_flac.h
Normal file
17
src/output/output_flac.h
Normal file
|
@ -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;
|
Loading…
Add table
Reference in a new issue