AAC input

This commit is contained in:
Marco 2020-10-28 15:42:33 +01:00 committed by Thulinma
parent 12246581e6
commit e961d71c16
5 changed files with 363 additions and 0 deletions

View file

@ -511,6 +511,8 @@ if(RIST_LIB)
makeInput(TSRIST tsrist with_rist)#LTS makeInput(TSRIST tsrist with_rist)#LTS
endif() endif()
makeInput(AAC aac)
######################################## ########################################
# MistServer - Outputs # # MistServer - Outputs #
######################################## ########################################

View file

@ -133,6 +133,18 @@ namespace aac{
return res.str(); 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{ adts::operator bool() const{
return hasSync() && len && len >= getHeaderSize() && getFrequency() && getChannelCount() && return hasSync() && len && len >= getHeaderSize() && getFrequency() && getChannelCount() &&
getSampleCount(); getSampleCount();

View file

@ -24,6 +24,7 @@ namespace aac{
bool hasSync() const; bool hasSync() const;
char *getPayload(); char *getPayload();
std::string toPrettyString() const; std::string toPrettyString() const;
std::string getInit() const;
operator bool() const; operator bool() const;
private: private:

321
src/input/input_aac.cpp Normal file
View file

@ -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 <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_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 <idx> 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 <seekTime> timestamp of the DTSH entry containing required file pos info
// @param <idx> 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

27
src/input/input_aac.h Normal file
View file

@ -0,0 +1,27 @@
#include "input.h"
#include <mist/dtsc.h>
#include <mist/adts.h>
#include <mist/urireader.h>
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;