diff --git a/CMakeLists.txt b/CMakeLists.txt index f8da7e32..20b5ebc1 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -169,6 +169,7 @@ set(libHeaders ${SOURCE_DIR}/lib/vorbis.h ${SOURCE_DIR}/lib/triggers.h ${SOURCE_DIR}/lib/opus.h + ${SOURCE_DIR}/lib/riff.h ) ######################################## @@ -215,6 +216,7 @@ set(libSources ${SOURCE_DIR}/lib/vorbis.cpp ${SOURCE_DIR}/lib/triggers.cpp ${SOURCE_DIR}/lib/opus.cpp + ${SOURCE_DIR}/lib/riff.cpp ) ######################################## @@ -282,6 +284,7 @@ makeAnalyser(TS ts) #LTS makeAnalyser(MP4 mp4) #LTS makeAnalyser(H264 h264) #LTS makeAnalyser(HLS hls) #LTS +makeAnalyser(RIFF riff) #LTS #LTS_START ######################################## diff --git a/lib/bitfields.h b/lib/bitfields.h index 4a2a2721..186a064c 100644 --- a/lib/bitfields.h +++ b/lib/bitfields.h @@ -68,5 +68,58 @@ namespace Bit{ p[7] = val & 0xFF; } + /// Retrieves a short in little endian from the pointer p. + inline unsigned short btohs_le(const char * p) { + return ((unsigned short)p[1] << 8) | p[0]; + } + + /// Stores a short value of val in little endian to the pointer p. + inline void htobs_le(char * p, unsigned short val) { + p[1] = (val >> 8) & 0xFF; + p[0] = val & 0xFF; + } + + /// Retrieves a long in network order from the pointer p. + inline unsigned long btohl_le(const char * p) { + return ((unsigned long)p[3] << 24) | ((unsigned long)p[2] << 16) | ((unsigned long)p[1] << 8) | p[0]; + } + + /// Stores a long value of val in little endian to the pointer p. + inline void htobl_le(char * p, unsigned long val) { + p[3] = (val >> 24) & 0xFF; + p[2] = (val >> 16) & 0xFF; + p[1] = (val >> 8) & 0xFF; + p[0] = val & 0xFF; + } + + /// Retrieves a long in little endian from the pointer p. + inline unsigned long btoh24_le(const char * p) { + return ((unsigned long)p[2] << 16) | ((unsigned long)p[1] << 8) | p[0]; + } + + /// Stores a long value of val in network order to the pointer p. + inline void htob24_le(char * p, unsigned long val) { + p[2] = (val >> 16) & 0xFF; + p[1] = (val >> 8) & 0xFF; + p[0] = val & 0xFF; + } + + /// Retrieves a long long in little endian from the pointer p. + inline unsigned long long btohll_le(const char * p) { + return ((unsigned long long)p[7] << 56) | ((unsigned long long)p[6] << 48) | ((unsigned long long)p[5] << 40) | ((unsigned long long)p[4] << 32) | ((unsigned long)p[3] << 24) | ((unsigned long)p[2] << 16) | ((unsigned long)p[1] << 8) | p[0]; + } + + /// Stores a long value of val in little endian to the pointer p. + inline void htobll_le(char * p, unsigned long long val) { + p[7] = (val >> 56) & 0xFF; + p[6] = (val >> 48) & 0xFF; + p[5] = (val >> 40) & 0xFF; + p[4] = (val >> 32) & 0xFF; + p[3] = (val >> 24) & 0xFF; + p[2] = (val >> 16) & 0xFF; + p[1] = (val >> 8) & 0xFF; + p[0] = val & 0xFF; + } + } diff --git a/lib/riff.cpp b/lib/riff.cpp new file mode 100644 index 00000000..0781db72 --- /dev/null +++ b/lib/riff.cpp @@ -0,0 +1,158 @@ +#include "riff.h" + +namespace RIFF{ + + Chunk::Chunk(const void *_p, uint32_t len){ + p = (const char *)_p; + if (len && len < getPayloadSize() + 8){ + FAIL_MSG("Chunk %s (%lub) does not fit in %lu bytes length!", getType().c_str(), + getPayloadSize() + 8, len); + p = 0; + } + } + + Chunk::Chunk(void *_p, const char * t, uint32_t len){ + p = (const char *)_p; + memcpy((void*)p, t, 4); + Bit::htobl_le((char*)p+4, len); + } + + void Chunk::toPrettyString(std::ostream &o, size_t indent) const{ + if (!p){ + o << std::string(indent, ' ') << "INVALID CHUNK" << std::endl; + return; + } + switch (Bit::btohl(p)){ + case 0x52494646lu: // RIFF + case 0x4C495354lu: // LIST + return ListChunk(p).toPrettyString(o, indent); + case 0x666D7420: // "fmt " + return fmt(p).toPrettyString(o, indent); + case 0x66616374: // fact + return fact(p).toPrettyString(o, indent); + case 0x49534654: // ISFT + return ISFT(p).toPrettyString(o, indent); + default: + o << std::string(indent, ' ') << "[" << getType() << "] UNIMPLEMENTED (" + << (getPayloadSize() + 8) << "b)" << std::endl; + } + } + + void ListChunk::toPrettyString(std::ostream &o, size_t indent) const{ + o << std::string(indent, ' ') << "[" << getType() << "] " << getIdentifier() << " (" + << (getPayloadSize() + 8) << "b):" << std::endl; + indent += 2; + uint32_t i = 12; + uint32_t len = getPayloadSize() + 8; + while (i + 8 <= len){ + const Chunk C(p + i); + C.toPrettyString(o, indent); + i += C.getPayloadSize() + 8; + if (!C){return;} + } + } + + uint16_t fmt::getFormat() const{ + if (!p){return 0;} + return Bit::btohs_le(p + 8); + } + std::string fmt::getCodec() const{ + switch (getFormat()){ + case 0x1: return "PCM"; + case 0x2: return "ADPCM"; + case 0x3: return "FLOAT"; + case 0x102: + case 0x172: + case 0x6: return "PCMA"; + case 0x101: + case 0x171: + case 0x7: return "PCMU"; + case 0x55: return "MP3"; + case 0xFFFE: // Extended, not implemented + default: return "?"; + } + } + uint16_t fmt::getChannels() const{ + if (!p){return 0;} + return Bit::btohs_le(p + 10); + } + uint32_t fmt::getHz() const{ + if (!p){return 0;} + return Bit::btohl_le(p + 12); + } + uint32_t fmt::getBPS() const{ + if (!p){return 0;} + return Bit::btohl_le(p + 16); + } + uint16_t fmt::getBlockSize() const{ + if (!p){return 0;} + return Bit::btohs_le(p + 20); + } + uint16_t fmt::getSize() const{ + if (!p){return 0;} + return Bit::btohs_le(p + 22); + } + uint16_t fmt::getExtLen() const{ + if (getPayloadSize() < 18){return 0;} + return Bit::btohs_le(p + 24); + } + uint16_t fmt::getValidBits() const{ + if (getPayloadSize() < 20 || getExtLen() < 2){return 0;} + return Bit::btohs_le(p + 26); + } + uint32_t fmt::getChannelMask() const{ + if (getPayloadSize() < 24 || getExtLen() < 6){return 0;} + return Bit::btohl_le(p + 28); + } + std::string fmt::getGUID() const{ + if (getPayloadSize() < 40 || getExtLen() < 22){return "";} + return std::string(p + 32, 16); + } + void fmt::toPrettyString(std::ostream &o, size_t indent) const{ + o << std::string(indent, ' ') << "[" << getType() << "] (" << (getPayloadSize() + 8) + << "b):" << std::endl; + indent += 1; + o << std::string(indent, ' ') << "Codec: " << getCodec() << " (" << getFormat() << ")" + << std::endl; + o << std::string(indent, ' ') << "Channels: " << getChannels() << std::endl; + o << std::string(indent, ' ') << "Sample rate: " << getHz() << "Hz" << std::endl; + o << std::string(indent, ' ') << "Bytes/s: " << getBPS() << std::endl; + o << std::string(indent, ' ') << "Block size: " << getBlockSize() << " bytes" << std::endl; + o << std::string(indent, ' ') << "Sample size: " << getSize() << " bits" << std::endl; + if (getExtLen()){ + o << std::string(indent, ' ') << "-- extended " << getExtLen() << "bytes --" << std::endl; + if (getExtLen() >= 2){ + o << std::string(indent, ' ') << "Valid bits: " << getValidBits() << std::endl; + } + if (getExtLen() >= 6){ + o << std::string(indent, ' ') << "Channel mask: " << getChannelMask() << std::endl; + } + if (getExtLen() >= 22){ + o << std::string(indent, ' ') << "GUID: " << getGUID() << std::endl; + } + } + } + + uint32_t fact::getSamplesPerChannel() const{ + if (!p){return 0;} + return Bit::btohl_le(p + 8); + } + void fact::toPrettyString(std::ostream &o, size_t indent) const{ + o << std::string(indent, ' ') << "[" << getType() << "] (" << (getPayloadSize() + 8) + << "b):" << std::endl; + indent += 1; + o << std::string(indent, ' ') << "Samples per channel: " << getSamplesPerChannel() << std::endl; + } + + std::string ISFT::getSoftware() const{ + if (!p){return 0;} + return std::string(p+8, getPayloadSize()); + } + void ISFT::toPrettyString(std::ostream &o, size_t indent) const{ + o << std::string(indent, ' ') << "[" << getType() << "] (" << (getPayloadSize() + 8) + << "b):" << std::endl; + indent += 1; + o << std::string(indent, ' ') << "Software: " << getSoftware() << std::endl; + } +} + diff --git a/lib/riff.h b/lib/riff.h new file mode 100644 index 00000000..72dc7fe5 --- /dev/null +++ b/lib/riff.h @@ -0,0 +1,77 @@ +#pragma once +#include "bitfields.h" +#include "defines.h" +#include + +namespace RIFF{ + + /// Basic RIFF chunk class - can only read type and size. + /// All RIFF chunks have this format. + class Chunk{ + public: + Chunk(const void *_p = 0, uint32_t len = 0); + Chunk(void *_p, const char * t, uint32_t len); + inline operator bool() const{return p;} + inline std::string getType() const{ + if (!p){return "";} + return std::string(p, 4); + } + inline uint32_t getPayloadSize() const{ + if (!p){return 0;} + return Bit::btohl_le(p + 4); + } + virtual void toPrettyString(std::ostream &o, size_t indent = 0) const; + + protected: + const char *p; + }; + + /// List-type RIFF chunk. Can read list identifier. + /// "RIFF" and "LIST" type chunks follow this format. + class ListChunk : public Chunk{ + public: + ListChunk(const void *_p = 0, uint32_t len = 0) : Chunk(_p, len){} + inline std::string getIdentifier() const{ + if (!p){return "";} + return std::string(p + 8, 4); + } + virtual void toPrettyString(std::ostream &o, size_t indent = 0) const; + }; + + /// WAVE "fmt " class. + class fmt : public Chunk{ + public: + fmt(const void *_p = 0, uint32_t len = 0) : Chunk(_p, len){} + uint16_t getFormat() const; + std::string getCodec() const; + uint16_t getChannels() const; + uint32_t getHz() const; + uint32_t getBPS() const; + uint16_t getBlockSize() const; + uint16_t getSize() const; + uint16_t getExtLen() const; + uint16_t getValidBits() const; + uint32_t getChannelMask() const; + std::string getGUID() const; + virtual void toPrettyString(std::ostream &o, size_t indent = 0) const; + }; + + /// WAVE fact class. + class fact : public Chunk { + public: + fact(const void *_p = 0, uint32_t len = 0) : Chunk(_p, len){} + uint32_t getSamplesPerChannel() const; + virtual void toPrettyString(std::ostream &o, size_t indent = 0) const; + }; + + /// ISFT class. Contains software name. + class ISFT : public Chunk { + public: + ISFT(const void *_p = 0, uint32_t len = 0) : Chunk(_p, len){} + std::string getSoftware() const; + virtual void toPrettyString(std::ostream &o, size_t indent = 0) const; + }; + + +} + diff --git a/src/analysers/analyser_riff.cpp b/src/analysers/analyser_riff.cpp new file mode 100644 index 00000000..b8b0ef80 --- /dev/null +++ b/src/analysers/analyser_riff.cpp @@ -0,0 +1,44 @@ +#include "analyser_riff.h" +#include +#include + +void AnalyserRIFF::init(Util::Config &conf){ + Analyser::init(conf); +} + +AnalyserRIFF::AnalyserRIFF(Util::Config &conf) : Analyser(conf){ + curPos = prePos = 0; +} + +bool AnalyserRIFF::parsePacket(){ + prePos = curPos; + // Read in smart bursts until we have enough data + while (isOpen() && dataBuffer.size() < neededBytes()){ + uint64_t needed = neededBytes(); + dataBuffer.reserve(needed); + for (uint64_t i = dataBuffer.size(); i < needed; ++i){ + dataBuffer += std::cin.get(); + ++curPos; + if (!std::cin.good()){dataBuffer.erase(dataBuffer.size() - 1, 1);} + } + } + + if (dataBuffer.size() < 8){return false;} + + RIFF::Chunk C(dataBuffer.data(), dataBuffer.size()); + INFO_MSG("Read a chunk at position %d", prePos); + if (detail >= 2){C.toPrettyString(std::cout);} + ///\TODO update mediaTime with the current timestamp + if (C){ + dataBuffer.erase(0, C.getPayloadSize()+8); + return true; + } + return false; +} + +/// Calculates how many bytes we need to read a whole box. +uint64_t AnalyserRIFF::neededBytes(){ + if (dataBuffer.size() < 8){return 8;} + return RIFF::Chunk(dataBuffer.data()).getPayloadSize()+8; +} + diff --git a/src/analysers/analyser_riff.h b/src/analysers/analyser_riff.h new file mode 100644 index 00000000..9aa8be2a --- /dev/null +++ b/src/analysers/analyser_riff.h @@ -0,0 +1,15 @@ +#include "analyser.h" + +class AnalyserRIFF : public Analyser{ +public: + AnalyserRIFF(Util::Config &conf); + static void init(Util::Config &conf); + bool parsePacket(); + +private: + uint64_t neededBytes(); + std::string dataBuffer; + uint64_t curPos; + uint64_t prePos; +}; +