From e961d71c16588c1bf5615772a06284016dda5daa Mon Sep 17 00:00:00 2001 From: Marco Date: Wed, 28 Oct 2020 15:42:33 +0100 Subject: [PATCH] AAC input --- CMakeLists.txt | 2 + lib/adts.cpp | 12 ++ lib/adts.h | 1 + src/input/input_aac.cpp | 321 ++++++++++++++++++++++++++++++++++++++++ src/input/input_aac.h | 27 ++++ 5 files changed, 363 insertions(+) create mode 100644 src/input/input_aac.cpp create mode 100644 src/input/input_aac.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 51dca4da..da04832f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -511,6 +511,8 @@ if(RIST_LIB) makeInput(TSRIST tsrist with_rist)#LTS endif() +makeInput(AAC aac) + ######################################## # MistServer - Outputs # ######################################## diff --git a/lib/adts.cpp b/lib/adts.cpp index 82092394..b0f44e29 100644 --- a/lib/adts.cpp +++ b/lib/adts.cpp @@ -133,6 +133,18 @@ namespace aac{ return res.str(); } + // Returns Init info used to init DTSC audio track + std::string adts::getInit() const{ + std::string init; + init.resize(2); + + init[0] = ((getAACProfile() & 0x1F) << 3) | + ((getFrequencyIndex() & 0x0E) >> 1); + init[1] = ((getFrequencyIndex() & 0x01) << 7) | + ((getChannelConfig() & 0x0F) << 3); + + return init; + } adts::operator bool() const{ return hasSync() && len && len >= getHeaderSize() && getFrequency() && getChannelCount() && getSampleCount(); diff --git a/lib/adts.h b/lib/adts.h index 0b7db493..56fb96d8 100644 --- a/lib/adts.h +++ b/lib/adts.h @@ -24,6 +24,7 @@ namespace aac{ bool hasSync() const; char *getPayload(); std::string toPrettyString() const; + std::string getInit() const; operator bool() const; private: diff --git a/src/input/input_aac.cpp b/src/input/input_aac.cpp new file mode 100644 index 00000000..6cd13ca8 --- /dev/null +++ b/src/input/input_aac.cpp @@ -0,0 +1,321 @@ +/* + * This file adds AAC input capabilities + * Dependent on lib/adts and lib/urireader + * + * Input can be any AAC file which consists of ADTS frames. + * + * NOTE some .AAC files are containers (eg .M4A) with an AAC audio track in it. + * Since these files do not consist solely of ADTS frames, they can not be parsed + * + * NOTE All output AAC's are MPEG-4, while inputs can be MPEG-2. This will cause + * a slight different header (FFF1 instead of FFF0) but this is fine + * + * NOTE: The adts_buffer_fullness and number_of_raw_data_blocks_in_frame are different + * in the input and output file + * + * NOTE: sometimes AAC files have metadata at the end. This gets removed for now. + * + * + * Other useful info for debugging: + * ADTS Fixed Header Structure: + * Item # Bits Note Bit# Byte# + * syncword 12 0xFFF 0-11 0-1 (bits 0-15) + * ID 1 12 1 (bits 8-15) + * layer 2 13-14 1 (bits 8-15) + * protection_absent 1 15 2 (bits 16-23) + * profile_ObjectType 2 16-17 2 (bits 16-23) + * sampling_frequency_index 4 18-21 2 (bits 16-23) + * private_bit 1 22 2 (bits 16-23) + * channel_configuration 3 23-25 2-3 (bits 16-31) + * original_copy 1 26 3 (bits 24-31) + * home 1 27 3 (bits 24-31) + * == 28 bits + * ADTS Variable Header Structure + * Item # Bits Note + * copyright_identification_bit 1 28 3 (bits 24-31) + * copyright_identification_start 1 29 3 (bits 24-31) + * aac_frame_length 13 30-42 3-4-5 (bits 24-47) + * adts_buffer_fullness 11 43-53 5-6 (bits 40-55) + * number_of_raw_data_blocks_in_frame 2 54-55 6 (bits 48-55) + * == 28 bits + * The rest of the ADTS data is the data itself with CRC info for detecting + * changes in sent/received files + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include //for stat +#include //for stat +#include //for stat +#include "input_aac.h" + +namespace Mist{ + inputAAC::inputAAC(Util::Config *cfg) : Input(cfg){ + capa["name"] = "AAC"; + capa["desc"] = "Allows loading AAC files"; + capa["source_match"] = "/*.aac"; + capa["source_file"] = "$source"; + capa["priority"] = 9; + capa["codecs"][0u][1u].append("AAC"); + timestamp = 0; + // init filePos at 1, else a 15 bit mismatch in expected frame size occurs + // dtsc.ccp +- line 215 + // ( bpos, if >= 0, adds 9 bytes (integer type) and 6 bytes (2+namelen) ) + // but at line 224: (packBytePos ? 15 : 0) + filePos = 1; + } + + inputAAC::~inputAAC(){} + + bool inputAAC::checkArguments(){ + 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 inputAAC::preRun(){ + inFile.open(config->getString("input")); + if (!inFile || inFile.isEOF()){return false;} + + struct stat statData; + lastModTime = 0; + + if (stat(config->getString("input").c_str(), &statData) != -1){ + lastModTime = statData.st_mtime; + } + return true; + } + + // Overrides the default keepRunning function to shut down + // if the file disappears or changes, by polling the file's mtime. + // If neither applies, calls the original function. + bool inputAAC::keepRunning(){ + struct stat statData; + if (stat(config->getString("input").c_str(), &statData) == -1){ + INFO_MSG("Shutting down because input file disappeared"); + return false; + } + if (lastModTime != statData.st_mtime){ + INFO_MSG("Shutting down because input file changed"); + return false; + } + return Input::keepRunning(); + } + + + // Reads the first frame to init track + // Then calls getNext untill all other frames have been added to the DTSH file + bool inputAAC::readHeader(){ + char *aacData; + char *aacFrame; + uint64_t frameSize = 0; + size_t bytesRead = 0; + + if (!inFile || inFile.isEOF()){ + INFO_MSG("Could not open input stream"); + return false; + } + + DONTEVEN_MSG("Parsing first ADTS frame..."); + + // Read fixed + variable header + inFile.readSome(aacData, bytesRead, 6); + if (bytesRead < 6){ + WARN_MSG("Not enough bytes left in buffer. Quitting..."); + // Dump for debug purposes + INFO_MSG("Header contains bytes: %x %x %x %x %x %x", aacData[0] + , aacData[1], aacData[2], aacData[3], aacData[4], aacData[5]); + return false; + } + // Confirm syncword (= FFF) + if (aacData[0] != 0xFF || (aacData[1] & 0xF0) != 0xF0){ + WARN_MSG("Invalid sync word at start of header"); + return false; + } + // Calculate the starting position of the next frame + frameSize = (((aacData[3] & 0x03) << 11) | (aacData[4] << 3) | ((aacData[5] >> 5) & 0x07)); + // Copy AAC header info + aacFrame = (char*)malloc(frameSize); + for (int i = 0; i < 6; i++){ + aacFrame[i] = aacData[i]; + } + + // Read the rest of the AAC frame + inFile.readSome(aacData, bytesRead, frameSize - 6); + if (bytesRead < frameSize - 6){ + WARN_MSG("Not enough bytes left in buffer."); + WARN_MSG("Wanted %li bytes but read %li bytes...", frameSize - 6, bytesRead); + } + for (int i = 0; i < (frameSize - 6); i++){ + aacFrame[i+6] = aacData[i]; + } + + // Create ADTS object of complete frame info + aac::adts adtsPack(aacFrame, frameSize); + if (!adtsPack){ + WARN_MSG("Could not parse ADTS package!"); + return false; + } + + // Init track info + meta.reInit(config->getString("streamname")); + size_t audioTrack = meta.addTrack(); + meta.setID(audioTrack, audioTrack); + meta.setInit(audioTrack, adtsPack.getInit()); + meta.setType(audioTrack, "audio"); + meta.setCodec(audioTrack, "AAC"); + meta.setRate(audioTrack, adtsPack.getFrequency()); + meta.setChannels(audioTrack, adtsPack.getChannelCount()); + + // Add current frame info + thisPacket.genericFill(timestamp, 0, audioTrack, adtsPack.getPayload(), adtsPack.getPayloadSize(), filePos, false); + meta.update(thisPacket); + + // Update internal variables + timestamp += (adtsPack.getSampleCount() * 1000) / adtsPack.getFrequency(); + filePos += frameSize; + + // Parse the rest of the ADTS frames + getNext(audioTrack); + while (thisPacket){ + meta.update(thisPacket); + getNext(audioTrack); + } + + if (!inFile.seek(0)) + ERROR_MSG("Could not seek back to position 0!"); + timestamp = 0; + M.toFile(config->getString("input") + ".dtsh"); + + return true; + } + + // Reads the ADTS frame at the current position then updates thisPacket + // @param contains the trackID to which we want to add the ADTS payload + void inputAAC::getNext(size_t idx){ + DONTEVEN_MSG("Parsing next ADTS frame..."); + // Temp variable which points to the urireader buffer so that we can copy this data + char *aacData; + // Will contain a local copy of uriReader/fileIn buffer in order to fill thisPacket + char *aacFrame; + // Temp variable which stores the frame size as defined in the first + // 6 bytes of the ADTS frame + uint64_t frameSize = 0; + // Temp variable which gets incremented with bytesRead to indicate the start + // pos of next ADTS frame + size_t nextFramePos = filePos; + // Temp var which indicates how many bytes the urireader put into the buffer + size_t bytesRead = 0; + // Amount of bytes to subtract from expected payload if the found payload + // is smaller than the ADTS header specifies + size_t disregardAmount = 0; + //packets should be initialised to null to ensure termination + thisPacket.null(); + + if (!inFile || inFile.isEOF()){ + INFO_MSG("Reached EOF"); + return; + } + + // Read fixed + variable header + inFile.readSome(aacData, bytesRead, 6); + if (bytesRead < 6){ + WARN_MSG("Not enough bytes left in buffer to extract a new ADTS frame"); + WARN_MSG("Wanted %i bytes but read %li bytes...", 6, bytesRead); + WARN_MSG("Header contains bytes: %x %x %x %x %x %x", aacData[0] + , aacData[1], aacData[2], aacData[3], aacData[4], aacData[5]); + return; + } + // Confirm syncword (= FFF) + if (aacData[0] != 0xFF || (aacData[1] & 0xF0) != 0xF0){ + // Check for APE tag (metadata, which we throw for now) + if (aacData[0] == 0x41 && aacData[1] == 0x50 && aacData[2] == 0x45 && + aacData[3] == 0x54 && aacData[4] == 0x41 && aacData[5] == 0x47){ + inFile.readAll(aacData, bytesRead); + INFO_MSG("Throwing out %li bytes of metadata...", bytesRead); + return; + } + WARN_MSG("Invalid sync word at start of header"); + return; + } + // Calculate the starting position of the next frame + frameSize = (((aacData[3] & 0x03) << 11) | (aacData[4] << 3) | ((aacData[5] >> 5) & 0x07)); + nextFramePos += frameSize; + // Copy AAC header info + aacFrame = (char*)malloc(frameSize); + for (int i = 0; i < 6; i++){ + aacFrame[i] = aacData[i]; + } + // Read the rest of the AAC frame + inFile.readSome(aacData, bytesRead, frameSize - 6); + if (bytesRead < frameSize - 6){ + WARN_MSG("Not enough bytes left in buffer."); + WARN_MSG("Wanted %li bytes but read %li bytes...", frameSize - 6, bytesRead); + disregardAmount = frameSize - 6 - bytesRead; + } + for (int i = 0; i < (frameSize - 6); i++){ + aacFrame[i+6] = aacData[i]; + } + + // Create ADTS object of frame + aac::adts adtsPack(aacFrame, frameSize); + if (!adtsPack){ + WARN_MSG("Could not parse ADTS package!"); + WARN_MSG("Current frame info:"); + WARN_MSG("Current frame pos: %li", filePos); + WARN_MSG("Next frame pos: %li", nextFramePos); + WARN_MSG("Frame size expected: %li", frameSize); + WARN_MSG("Bytes read: %li", bytesRead); + WARN_MSG("ADTS getAACProfile: %li", adtsPack.getAACProfile()); + WARN_MSG("ADTS getFrequencyIndex: %li", adtsPack.getFrequencyIndex()); + WARN_MSG("ADTS getFrequency: %li", adtsPack.getFrequency()); + WARN_MSG("ADTS getChannelConfig: %li", adtsPack.getChannelConfig()); + WARN_MSG("ADTS getChannelCount: %li", adtsPack.getChannelCount()); + WARN_MSG("ADTS getHeaderSize: %li", adtsPack.getHeaderSize()); + WARN_MSG("ADTS getPayloadSize: %li", adtsPack.getPayloadSize()); + WARN_MSG("ADTS getCompleteSize: %li", adtsPack.getCompleteSize()); + WARN_MSG("ADTS getSampleCount: %li", adtsPack.getSampleCount()); + return; + } + + thisPacket.genericFill(timestamp, 0, idx, adtsPack.getPayload(), adtsPack.getPayloadSize() - disregardAmount, filePos, false); + + //Update the internal timestamp + timestamp += (adtsPack.getSampleCount() * 1000) / adtsPack.getFrequency(); + filePos = nextFramePos; + } + + // Seeks to the filePos + // @param timestamp of the DTSH entry containing required file pos info + // @param trackID of the AAC track + void inputAAC::seek(uint64_t seekTime, size_t idx){ + DTSC::Keys keys(M.keys(idx)); + uint32_t keyNum = keys.getNumForTime(seekTime); + // We minus the filePos by one, since we init it 1 higher + inFile.seek(keys.getBpos(keyNum)-1); + timestamp = keys.getTime(keyNum); + DONTEVEN_MSG("inputAAC wants to seek to timestamp %li on track %li", seekTime, idx); + DONTEVEN_MSG("inputAAC seeked to timestamp %f with bytePos %li", timestamp, keys.getBpos(keyNum)-1); + } +}// namespace Mist + + + diff --git a/src/input/input_aac.h b/src/input/input_aac.h new file mode 100644 index 00000000..f28867c2 --- /dev/null +++ b/src/input/input_aac.h @@ -0,0 +1,27 @@ +#include "input.h" +#include +#include +#include + +namespace Mist{ + class inputAAC : public Input{ + public: + inputAAC(Util::Config *cfg); + ~inputAAC(); + + protected: + // Private Functions + bool checkArguments(); + bool preRun(); + bool readHeader(); + void seek(uint64_t seekTime, size_t idx); + void getNext(size_t idx = INVALID_TRACK_ID); + bool keepRunning(); + uint64_t lastModTime; + HTTP::URIReader inFile; + double timestamp; + size_t filePos; + }; +}// namespace Mist + +typedef Mist::inputAAC mistIn;