mistserver/src/input/input_aac.cpp
Eli Mallon e324c2ee58 refactor: capitalize Input classes, rename srt to subrip in source as well
Co-authored-by: Thulinma <jaron@vietors.com>
2024-05-16 16:07:49 +02:00

319 lines
12 KiB
C++

/*
* 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"]["audio"].append("AAC");
thisTime = 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;
audioTrack = INVALID_TRACK_ID;
}
InputAAC::~InputAAC(){}
bool InputAAC::checkArguments(){
if (!config->getString("streamname").size()){
if (config->getString("output") == "-"){
Util::logExitReason(ER_FORMAT_SPECIFIC, "Output to stdout not yet supported");
return false;
}
}else{
if (config->getString("output") != "-"){
Util::logExitReason(ER_FORMAT_SPECIFIC, "File output in player mode not supported");
return false;
}
}
return true;
}
bool InputAAC::preRun(){
inFile.open(config->getString("input"));
if (!inFile || inFile.isEOF()){
Util::logExitReason(ER_READ_START_FAILURE, "Reading header for '%s' failed: Could not open input stream", config->getString("input").c_str());
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()){
Util::logExitReason(ER_READ_START_FAILURE, "Reading header for '%s' failed: Could not open input stream", config->getString("input").c_str());
return false;
}
DONTEVEN_MSG("Parsing first ADTS frame...");
// Read fixed + variable header
inFile.readSome(aacData, bytesRead, 6);
if (bytesRead < 6){
Util::logExitReason(ER_READ_START_FAILURE, "Reading header for '%s' failed: Not enough bytes left in buffer", config->getString("input").c_str());
return false;
}
// Confirm syncword (= FFF)
if (aacData[0] != 0xFF || (aacData[1] & 0xF0) != 0xF0){
Util::logExitReason(ER_FORMAT_SPECIFIC, "Reading header for '%s' failed: Invalid sync word at start of header", config->getString("input").c_str());
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 %" PRIu64 " bytes but read %zu 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){
Util::logExitReason(ER_FORMAT_SPECIFIC, "Reading header for '%s' failed: Could not parse ADTS package", config->getString("input").c_str());
return false;
}
// Init track info
meta.reInit(config->getString("streamname"));
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(thisTime, 0, audioTrack, adtsPack.getPayload(), adtsPack.getPayloadSize(), filePos, false);
meta.update(thisPacket);
// Update internal variables
thisTime += (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!");
thisTime = 0;
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){
//packets should be initialised to null to ensure termination
thisPacket.null();
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;
if (!inFile || inFile.isEOF()){
Util::logExitReason(ER_CLEAN_EOF, "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 6 bytes but read %zu bytes...", 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 %zu bytes of metadata...", bytesRead);
Util::logExitReason(ER_CLEAN_EOF, "Reached EOF");
return;
}
Util::logExitReason(ER_FORMAT_SPECIFIC, "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 %" PRIu64 " bytes but read %zu 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){
Util::logExitReason(ER_FORMAT_SPECIFIC, "Could not parse ADTS package");
return;
}
thisIdx = audioTrack;
thisPacket.genericFill(thisTime, 0, thisIdx, adtsPack.getPayload(), adtsPack.getPayloadSize() - disregardAmount, filePos, false);
//Update the internal timestamp
thisTime += (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){
if (audioTrack == INVALID_TRACK_ID){
std::set<size_t> trks = meta.getValidTracks();
if (trks.size()){
audioTrack = *(trks.begin());
}else{
Util::logExitReason(ER_FORMAT_SPECIFIC, "No audio track in header");
return;
}
}
DTSC::Keys keys(M.keys(audioTrack));
uint32_t keyIdx = M.getKeyIndexForTime(audioTrack, seekTime);
// We minus the filePos by one, since we init it 1 higher
inFile.seek(keys.getBpos(keyIdx)-1);
thisTime = keys.getTime(keyIdx);
DONTEVEN_MSG("inputAAC wants to seek to timestamp %" PRIu64 " on track %zu", seekTime, idx);
DONTEVEN_MSG("inputAAC seeked to timestamp %" PRIu64 " with bytePos %zu", thisTime, keys.getBpos(keyIdx)-1);
}
}// namespace Mist