mirror of
https://gitlab.com/mbunkus/mkvtoolnix.git
synced 2025-01-17 15:42:10 +00:00
AAC common: implement ADTS-to-raw parser & framing packet converter
This is groundwork for adding a LOAS/LATM parser on top of it.
This commit is contained in:
parent
fb3e6de77f
commit
6603d22ea3
@ -13,11 +13,7 @@
|
|||||||
|
|
||||||
#include "common/common_pch.h"
|
#include "common/common_pch.h"
|
||||||
|
|
||||||
#include <stdio.h>
|
|
||||||
#include <string.h>
|
|
||||||
|
|
||||||
#include "common/aac.h"
|
#include "common/aac.h"
|
||||||
#include "common/bit_cursor.h"
|
|
||||||
#include "common/codec.h"
|
#include "common/codec.h"
|
||||||
#include "common/strings/formatting.h"
|
#include "common/strings/formatting.h"
|
||||||
|
|
||||||
@ -25,6 +21,286 @@ const int g_aac_sampling_freq[16] = {96000, 88200, 64000, 48000, 44100, 32000,
|
|||||||
24000, 22050, 16000, 12000, 11025, 8000,
|
24000, 22050, 16000, 12000, 11025, 8000,
|
||||||
7350, 0, 0, 0}; // filling
|
7350, 0, 0, 0}; // filling
|
||||||
|
|
||||||
|
namespace aac {
|
||||||
|
|
||||||
|
frame_c::frame_c() {
|
||||||
|
init();
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
frame_c::init() {
|
||||||
|
m_id = 0;
|
||||||
|
m_profile = 0;
|
||||||
|
m_sample_rate = 0;
|
||||||
|
m_bit_rate = 0;
|
||||||
|
m_channels = 0;
|
||||||
|
m_frame_size = 0;
|
||||||
|
m_header_bit_size = 0;
|
||||||
|
m_header_byte_size = 0;
|
||||||
|
m_data_byte_size = 0;
|
||||||
|
m_stream_position = 0;
|
||||||
|
m_garbage_size = 0;
|
||||||
|
m_valid = false;
|
||||||
|
m_timecode.reset();
|
||||||
|
m_data.reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool
|
||||||
|
frame_c::decode_adts_header(unsigned char const *buffer,
|
||||||
|
size_t buffer_size) {
|
||||||
|
try {
|
||||||
|
init();
|
||||||
|
|
||||||
|
auto bc = bit_reader_c{buffer, static_cast<unsigned int>(buffer_size)};
|
||||||
|
|
||||||
|
if (bc.get_bits(12) != 0xfff) // ADTS header
|
||||||
|
return false;
|
||||||
|
|
||||||
|
m_id = bc.get_bit(); // ID: 0 = MPEG-4, 1 = MPEG-2
|
||||||
|
if (bc.get_bits(2) != 0) // layer == 0 !
|
||||||
|
return false;
|
||||||
|
bool protection_absent = bc.get_bit();
|
||||||
|
m_profile = bc.get_bits(2);
|
||||||
|
int sfreq_index = bc.get_bits(4);
|
||||||
|
bc.skip_bits(1); // private
|
||||||
|
m_channels = bc.get_bits(3);
|
||||||
|
bc.skip_bits(1 + 1); // original/copy & home
|
||||||
|
bc.skip_bits(1 + 1); // copyright_id_bit & copyright_id_start
|
||||||
|
|
||||||
|
m_frame_size = bc.get_bits(13);
|
||||||
|
m_header_bit_size = 56 + (!protection_absent ? 16 : 0);
|
||||||
|
|
||||||
|
if (m_header_bit_size >= m_frame_size * 8)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
bc.skip_bits(11); // adts_buffer_fullness
|
||||||
|
bc.skip_bits(2); // no_raw_blocks_in_frame
|
||||||
|
if (!protection_absent)
|
||||||
|
bc.skip_bits(16);
|
||||||
|
|
||||||
|
m_sample_rate = g_aac_sampling_freq[sfreq_index];
|
||||||
|
m_bit_rate = 1024;
|
||||||
|
m_header_byte_size = (m_header_bit_size + 7) / 8;
|
||||||
|
m_data_byte_size = m_frame_size - m_header_byte_size;
|
||||||
|
m_valid = m_header_bit_size != 0;
|
||||||
|
|
||||||
|
} catch (mtx::mm_io::end_of_file_x &) {
|
||||||
|
}
|
||||||
|
|
||||||
|
return m_valid;
|
||||||
|
}
|
||||||
|
|
||||||
|
int
|
||||||
|
frame_c::find_in(memory_cptr const &buffer) {
|
||||||
|
return find_in(buffer->get_buffer(), buffer->get_size());
|
||||||
|
}
|
||||||
|
|
||||||
|
int
|
||||||
|
frame_c::find_in(unsigned char const *buffer,
|
||||||
|
size_t buffer_size) {
|
||||||
|
for (size_t offset = 0; offset < buffer_size; ++offset)
|
||||||
|
if (decode_adts_header(&buffer[offset], buffer_size - offset))
|
||||||
|
return offset;
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string
|
||||||
|
frame_c::to_string(bool verbose)
|
||||||
|
const {
|
||||||
|
if (!verbose)
|
||||||
|
return (boost::format("position %1% size %2% ID %3% profile %4%") % m_stream_position % m_frame_size % m_id % m_profile).str();
|
||||||
|
|
||||||
|
return (boost::format("position %1% size %2% garbage %3% ID %4% profile %5% sample rate %6% bit rate %7% channels %8%")
|
||||||
|
% m_stream_position
|
||||||
|
% m_frame_size
|
||||||
|
% m_garbage_size
|
||||||
|
% m_id
|
||||||
|
% m_profile
|
||||||
|
% m_sample_rate
|
||||||
|
% m_bit_rate
|
||||||
|
% m_channels
|
||||||
|
).str();
|
||||||
|
}
|
||||||
|
|
||||||
|
// ------------------------------------------------------------
|
||||||
|
|
||||||
|
parser_c::parser_c()
|
||||||
|
: m_parsed_stream_position{}
|
||||||
|
, m_total_stream_position{}
|
||||||
|
, m_garbage_size{}
|
||||||
|
, m_multiplex_type{unknown_multiplex}
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
parser_c::add_timecode(timecode_c const &timecode) {
|
||||||
|
m_provided_timecodes.push_back(timecode);
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
parser_c::add_bytes(memory_cptr const &mem) {
|
||||||
|
add_bytes(mem->get_buffer(), mem->get_size());
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
parser_c::add_bytes(unsigned char *const buffer,
|
||||||
|
size_t size) {
|
||||||
|
m_buffer.add(buffer, size);
|
||||||
|
m_total_stream_position += size;
|
||||||
|
parse();
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
parser_c::flush() {
|
||||||
|
// no-op
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t
|
||||||
|
parser_c::frames_available()
|
||||||
|
const {
|
||||||
|
return m_frames.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
frame_c
|
||||||
|
parser_c::get_frame() {
|
||||||
|
auto frame = m_frames.front();
|
||||||
|
m_frames.pop_front();
|
||||||
|
return frame;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint64_t
|
||||||
|
parser_c::get_total_stream_position()
|
||||||
|
const {
|
||||||
|
return m_total_stream_position;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint64_t
|
||||||
|
parser_c::get_parsed_stream_position()
|
||||||
|
const {
|
||||||
|
return m_parsed_stream_position;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool
|
||||||
|
parser_c::decode_header(unsigned char const *buffer,
|
||||||
|
size_t buffer_size,
|
||||||
|
frame_c &frame) {
|
||||||
|
if (m_multiplex_type == adts_multiplex)
|
||||||
|
return frame.decode_adts_header(buffer, buffer_size);
|
||||||
|
|
||||||
|
if (frame.decode_adts_header(buffer, buffer_size)) {
|
||||||
|
m_multiplex_type = adts_multiplex;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
parser_c::parse() {
|
||||||
|
auto buffer = m_buffer.get_buffer();
|
||||||
|
auto buffer_size = m_buffer.get_size();
|
||||||
|
auto position = 0u;
|
||||||
|
|
||||||
|
while ((position + 8) < buffer_size) {
|
||||||
|
frame_c frame;
|
||||||
|
|
||||||
|
if (!decode_header(&buffer[position], buffer_size - position, frame)) {
|
||||||
|
++position;
|
||||||
|
++m_garbage_size;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((position + frame.m_frame_size) > buffer_size)
|
||||||
|
break;
|
||||||
|
|
||||||
|
if (!m_provided_timecodes.empty()) {
|
||||||
|
frame.m_timecode = m_provided_timecodes.front();
|
||||||
|
m_provided_timecodes.pop_front();
|
||||||
|
}
|
||||||
|
|
||||||
|
frame.m_data = memory_c::clone(&buffer[position + frame.m_header_byte_size], frame.m_data_byte_size);
|
||||||
|
frame.m_stream_position = m_parsed_stream_position + position;
|
||||||
|
frame.m_garbage_size = m_garbage_size;
|
||||||
|
|
||||||
|
m_garbage_size = 0;
|
||||||
|
position += frame.m_frame_size;
|
||||||
|
|
||||||
|
m_frames.push_back(frame);
|
||||||
|
}
|
||||||
|
|
||||||
|
m_buffer.remove(position);
|
||||||
|
m_parsed_stream_position += position;
|
||||||
|
}
|
||||||
|
|
||||||
|
int
|
||||||
|
parser_c::find_consecutive_frames(unsigned char const *buffer,
|
||||||
|
size_t buffer_size,
|
||||||
|
size_t num_required_headers) {
|
||||||
|
static auto s_debug = debugging_option_c{"aac_consecutive_frames"};
|
||||||
|
size_t base = 0;
|
||||||
|
|
||||||
|
do {
|
||||||
|
mxdebug_if(s_debug, boost::format("Starting search for %2% headers with base %1%, buffer size %3%\n") % base % num_required_headers % buffer_size);
|
||||||
|
|
||||||
|
size_t position = base;
|
||||||
|
|
||||||
|
frame_c first_frame;
|
||||||
|
while (((position + 8) < buffer_size) && !first_frame.decode_adts_header(&buffer[position], buffer_size - position))
|
||||||
|
++position;
|
||||||
|
|
||||||
|
mxdebug_if(s_debug, boost::format("First frame at %1% valid %2%\n") % position % first_frame.m_valid);
|
||||||
|
|
||||||
|
if (!first_frame.m_valid)
|
||||||
|
return -1;
|
||||||
|
|
||||||
|
size_t offset = position + first_frame.m_frame_size;
|
||||||
|
size_t num_headers_found = 1;
|
||||||
|
|
||||||
|
while ( (num_headers_found < num_required_headers)
|
||||||
|
&& (offset < buffer_size)) {
|
||||||
|
|
||||||
|
frame_c current_frame;
|
||||||
|
if (!current_frame.decode_adts_header(&buffer[offset], buffer_size - offset))
|
||||||
|
break;
|
||||||
|
|
||||||
|
if (8 > current_frame.m_frame_size) {
|
||||||
|
mxdebug_if(s_debug, boost::format("Current frame at %1% has invalid size %2%\n") % offset % current_frame.m_frame_size);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( (current_frame.m_id != first_frame.m_id)
|
||||||
|
&& (current_frame.m_profile != first_frame.m_profile)
|
||||||
|
&& (current_frame.m_channels != first_frame.m_channels)
|
||||||
|
&& (current_frame.m_sample_rate != first_frame.m_sample_rate)) {
|
||||||
|
mxdebug_if(s_debug,
|
||||||
|
boost::format("Current frame at %9% differs from first frame. (first/current) ID: %1%/%2% profile: %3%/%4% channels: %5%/%6% sample rate: %7%/%8%\n")
|
||||||
|
% first_frame.m_id % current_frame.m_id
|
||||||
|
% first_frame.m_profile % current_frame.m_profile
|
||||||
|
% first_frame.m_channels % current_frame.m_channels
|
||||||
|
% first_frame.m_sample_rate % current_frame.m_sample_rate
|
||||||
|
% offset);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
mxdebug_if(s_debug, boost::format("Current frame at %1% equals first frame, found %2%\n") % offset % (num_headers_found + 1));
|
||||||
|
|
||||||
|
++num_headers_found;
|
||||||
|
offset += current_frame.m_frame_size;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (num_headers_found == num_required_headers) {
|
||||||
|
mxdebug_if(s_debug, boost::format("Found required number of headers at %1%\n") % position);
|
||||||
|
return position;
|
||||||
|
}
|
||||||
|
|
||||||
|
base = position + 2;
|
||||||
|
} while (base < buffer_size);
|
||||||
|
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
aac_header_c::aac_header_c()
|
aac_header_c::aac_header_c()
|
||||||
: object_type{}
|
: object_type{}
|
||||||
, extension_object_type{}
|
, extension_object_type{}
|
||||||
|
@ -17,6 +17,8 @@
|
|||||||
#include "common/common_pch.h"
|
#include "common/common_pch.h"
|
||||||
|
|
||||||
#include "common/bit_cursor.h"
|
#include "common/bit_cursor.h"
|
||||||
|
#include "common/byte_buffer.h"
|
||||||
|
#include "common/timecode.h"
|
||||||
|
|
||||||
#define AAC_ID_MPEG4 0
|
#define AAC_ID_MPEG4 0
|
||||||
#define AAC_ID_MPEG2 1
|
#define AAC_ID_MPEG2 1
|
||||||
@ -63,6 +65,65 @@ protected:
|
|||||||
|
|
||||||
bool operator ==(const aac_header_c &h1, const aac_header_c &h2);
|
bool operator ==(const aac_header_c &h1, const aac_header_c &h2);
|
||||||
|
|
||||||
|
namespace aac {
|
||||||
|
|
||||||
|
class frame_c {
|
||||||
|
public:
|
||||||
|
unsigned int m_id, m_profile, m_sample_rate, m_bit_rate, m_channels, m_frame_size, m_header_bit_size, m_header_byte_size, m_data_byte_size;
|
||||||
|
uint64_t m_stream_position;
|
||||||
|
size_t m_garbage_size;
|
||||||
|
timecode_c m_timecode;
|
||||||
|
bool m_valid;
|
||||||
|
memory_cptr m_data;
|
||||||
|
|
||||||
|
public:
|
||||||
|
frame_c();
|
||||||
|
void init();
|
||||||
|
bool decode_adts_header(unsigned char const *buffer, size_t buffer_size);
|
||||||
|
|
||||||
|
std::string to_string(bool verbose = false) const;
|
||||||
|
|
||||||
|
int find_in(memory_cptr const &buffer);
|
||||||
|
int find_in(unsigned char const *buffer, size_t buffer_size);
|
||||||
|
};
|
||||||
|
|
||||||
|
class parser_c {
|
||||||
|
public:
|
||||||
|
enum multiplex_type_e {
|
||||||
|
unknown_multiplex = 0
|
||||||
|
, adts_multiplex
|
||||||
|
, adif_multiplex
|
||||||
|
};
|
||||||
|
|
||||||
|
protected:
|
||||||
|
std::deque<frame_c> m_frames;
|
||||||
|
std::deque<timecode_c> m_provided_timecodes;
|
||||||
|
byte_buffer_c m_buffer;
|
||||||
|
uint64_t m_parsed_stream_position, m_total_stream_position;
|
||||||
|
size_t m_garbage_size;
|
||||||
|
multiplex_type_e m_multiplex_type;
|
||||||
|
|
||||||
|
public:
|
||||||
|
parser_c();
|
||||||
|
void add_timecode(timecode_c const &timecode);
|
||||||
|
void add_bytes(memory_cptr const &mem);
|
||||||
|
void add_bytes(unsigned char *const buffer, size_t size);
|
||||||
|
void flush();
|
||||||
|
size_t frames_available() const;
|
||||||
|
frame_c get_frame();
|
||||||
|
uint64_t get_parsed_stream_position() const;
|
||||||
|
uint64_t get_total_stream_position() const;
|
||||||
|
|
||||||
|
int find_consecutive_frames(unsigned char const *buffer, size_t buffer_size, size_t num_required_headers);
|
||||||
|
|
||||||
|
protected:
|
||||||
|
void parse();
|
||||||
|
bool decode_header(unsigned char const *buffer, size_t buffer_size, frame_c &frame);
|
||||||
|
};
|
||||||
|
typedef std::shared_ptr<parser_c> parser_cptr;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
bool parse_aac_adif_header(const unsigned char *buf, int size, aac_header_c *aac_header);
|
bool parse_aac_adif_header(const unsigned char *buf, int size, aac_header_c *aac_header);
|
||||||
int find_aac_header(const unsigned char *buf, int size, aac_header_c *aac_header, bool emphasis_present);
|
int find_aac_header(const unsigned char *buf, int size, aac_header_c *aac_header, bool emphasis_present);
|
||||||
int find_consecutive_aac_headers(const unsigned char *buf, int size, int num);
|
int find_consecutive_aac_headers(const unsigned char *buf, int size, int num);
|
||||||
|
40
src/input/aac_framing_packet_converter.cpp
Normal file
40
src/input/aac_framing_packet_converter.cpp
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
/*
|
||||||
|
mkvmerge -- utility for splicing together matroska files
|
||||||
|
from component media subtypes
|
||||||
|
|
||||||
|
Distributed under the GPL v2
|
||||||
|
see the file COPYING for details
|
||||||
|
or visit http://www.gnu.org/copyleft/gpl.html
|
||||||
|
|
||||||
|
AAC framing type converter
|
||||||
|
|
||||||
|
Written by Moritz Bunkus <moritz@bunkus.org>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "common/common_pch.h"
|
||||||
|
|
||||||
|
#include "common/strings/formatting.h"
|
||||||
|
#include "merge/pr_generic.h"
|
||||||
|
#include "input/aac_framing_packet_converter.h"
|
||||||
|
|
||||||
|
aac_framing_packet_converter_c::aac_framing_packet_converter_c(generic_packetizer_c *ptzr)
|
||||||
|
: packet_converter_c{ptzr}
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
bool
|
||||||
|
aac_framing_packet_converter_c::convert(packet_cptr const &packet) {
|
||||||
|
if (packet->has_timecode()) {
|
||||||
|
m_parser.add_timecode(timecode_c::ns(packet->timecode));
|
||||||
|
}
|
||||||
|
|
||||||
|
m_parser.add_bytes(packet->data);
|
||||||
|
|
||||||
|
while (m_parser.frames_available()) {
|
||||||
|
auto frame = m_parser.get_frame();
|
||||||
|
auto packet_out = std::make_shared<packet_t>(frame.m_data, frame.m_timecode.to_ns(-1));
|
||||||
|
m_ptzr->process(packet_out);
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
33
src/input/aac_framing_packet_converter.h
Normal file
33
src/input/aac_framing_packet_converter.h
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
/*
|
||||||
|
mkvmerge -- utility for splicing together matroska files
|
||||||
|
from component media subtypes
|
||||||
|
|
||||||
|
Distributed under the GPL v2
|
||||||
|
see the file COPYING for details
|
||||||
|
or visit http://www.gnu.org/copyleft/gpl.html
|
||||||
|
|
||||||
|
class definitions for the AAC framing type converter
|
||||||
|
|
||||||
|
Written by Moritz Bunkus <moritz@bunkus.org>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef MTX_INPUT_AAC_FRAMING_PACKET_CONVERTER_H
|
||||||
|
#define MTX_INPUT_AAC_FRAMING_PACKET_CONVERTER_H
|
||||||
|
|
||||||
|
#include "common/common_pch.h"
|
||||||
|
|
||||||
|
#include "common/aac.h"
|
||||||
|
#include "input/packet_converter.h"
|
||||||
|
|
||||||
|
class aac_framing_packet_converter_c: public packet_converter_c {
|
||||||
|
protected:
|
||||||
|
aac::parser_c m_parser;
|
||||||
|
|
||||||
|
public:
|
||||||
|
aac_framing_packet_converter_c(generic_packetizer_c *ptzr);
|
||||||
|
virtual ~aac_framing_packet_converter_c() {};
|
||||||
|
|
||||||
|
virtual bool convert(packet_cptr const &packet);
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // MTX_INPUT_AAC_FRAMING_PACKET_CONVERTER_H
|
Loading…
Reference in New Issue
Block a user