395 lines
12 KiB
C
395 lines
12 KiB
C
/*
|
|
* Immersive Audio Model and Formats parsing
|
|
* Copyright (c) 2023 James Almer <[email protected]>
|
|
*
|
|
* This file is part of FFmpeg.
|
|
*
|
|
* FFmpeg is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU Lesser General Public
|
|
* License as published by the Free Software Foundation; either
|
|
* version 2.1 of the License, or (at your option) any later version.
|
|
*
|
|
* FFmpeg is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
* Lesser General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU Lesser General Public
|
|
* License along with FFmpeg; if not, write to the Free Software
|
|
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
|
*/
|
|
|
|
#include "libavutil/avassert.h"
|
|
#include "libavutil/intreadwrite.h"
|
|
#include "libavutil/log.h"
|
|
#include "libavutil/mem.h"
|
|
#include "libavcodec/get_bits.h"
|
|
#include "libavcodec/flac.h"
|
|
#include "libavcodec/leb.h"
|
|
#include "libavcodec/mpeg4audio.h"
|
|
#include "libavcodec/put_bits.h"
|
|
#include "avio_internal.h"
|
|
#include "iamf_parse.h"
|
|
#include "isom.h"
|
|
|
|
static int opus_decoder_config(IAMFCodecConfig *codec_config,
|
|
AVIOContext *pb, int len)
|
|
{
|
|
int ret, left = len - avio_tell(pb);
|
|
|
|
if (left < 11 || codec_config->audio_roll_distance >= 0)
|
|
return AVERROR_INVALIDDATA;
|
|
|
|
codec_config->extradata = av_malloc(left + 8);
|
|
if (!codec_config->extradata)
|
|
return AVERROR(ENOMEM);
|
|
|
|
AV_WN32A(codec_config->extradata, MKBETAG('O','p','u','s'));
|
|
AV_WN32A(codec_config->extradata + 4, MKBETAG('H','e','a','d'));
|
|
ret = ffio_read_size(pb, codec_config->extradata + 8, left);
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
codec_config->extradata_size = left + 8;
|
|
codec_config->sample_rate = 48000;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int aac_decoder_config(IAMFCodecConfig *codec_config,
|
|
AVIOContext *pb, int len, void *logctx)
|
|
{
|
|
MPEG4AudioConfig cfg = { 0 };
|
|
int object_type_id, codec_id, stream_type;
|
|
int ret, tag, left;
|
|
|
|
if (codec_config->audio_roll_distance >= 0)
|
|
return AVERROR_INVALIDDATA;
|
|
|
|
ff_mp4_read_descr(logctx, pb, &tag);
|
|
if (tag != MP4DecConfigDescrTag)
|
|
return AVERROR_INVALIDDATA;
|
|
|
|
object_type_id = avio_r8(pb);
|
|
if (object_type_id != 0x40)
|
|
return AVERROR_INVALIDDATA;
|
|
|
|
stream_type = avio_r8(pb);
|
|
if (((stream_type >> 2) != 5) || ((stream_type >> 1) & 1))
|
|
return AVERROR_INVALIDDATA;
|
|
|
|
avio_skip(pb, 3); // buffer size db
|
|
avio_skip(pb, 4); // rc_max_rate
|
|
avio_skip(pb, 4); // avg bitrate
|
|
|
|
codec_id = ff_codec_get_id(ff_mp4_obj_type, object_type_id);
|
|
if (codec_id && codec_id != codec_config->codec_id)
|
|
return AVERROR_INVALIDDATA;
|
|
|
|
left = ff_mp4_read_descr(logctx, pb, &tag);
|
|
if (tag != MP4DecSpecificDescrTag ||
|
|
!left || left > (len - avio_tell(pb)))
|
|
return AVERROR_INVALIDDATA;
|
|
|
|
// We pad extradata here because avpriv_mpeg4audio_get_config2() needs it.
|
|
codec_config->extradata = av_malloc((size_t)left + AV_INPUT_BUFFER_PADDING_SIZE);
|
|
if (!codec_config->extradata)
|
|
return AVERROR(ENOMEM);
|
|
|
|
ret = ffio_read_size(pb, codec_config->extradata, left);
|
|
if (ret < 0)
|
|
return ret;
|
|
codec_config->extradata_size = left;
|
|
memset(codec_config->extradata + codec_config->extradata_size, 0,
|
|
AV_INPUT_BUFFER_PADDING_SIZE);
|
|
|
|
ret = avpriv_mpeg4audio_get_config2(&cfg, codec_config->extradata,
|
|
codec_config->extradata_size, 1, logctx);
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
codec_config->sample_rate = cfg.sample_rate;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int flac_decoder_config(IAMFCodecConfig *codec_config,
|
|
AVIOContext *pb, int len)
|
|
{
|
|
int ret, left;
|
|
|
|
if (codec_config->audio_roll_distance)
|
|
return AVERROR_INVALIDDATA;
|
|
|
|
avio_skip(pb, 4); // METADATA_BLOCK_HEADER
|
|
|
|
left = len - avio_tell(pb);
|
|
if (left < FLAC_STREAMINFO_SIZE)
|
|
return AVERROR_INVALIDDATA;
|
|
|
|
codec_config->extradata = av_malloc(left);
|
|
if (!codec_config->extradata)
|
|
return AVERROR(ENOMEM);
|
|
|
|
ret = ffio_read_size(pb, codec_config->extradata, left);
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
codec_config->extradata_size = left;
|
|
codec_config->sample_rate = AV_RB24(codec_config->extradata + 10) >> 4;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int ipcm_decoder_config(IAMFCodecConfig *codec_config,
|
|
AVIOContext *pb, int len)
|
|
{
|
|
static const enum AVCodecID sample_fmt[2][3] = {
|
|
{ AV_CODEC_ID_PCM_S16BE, AV_CODEC_ID_PCM_S24BE, AV_CODEC_ID_PCM_S32BE },
|
|
{ AV_CODEC_ID_PCM_S16LE, AV_CODEC_ID_PCM_S24LE, AV_CODEC_ID_PCM_S32LE },
|
|
};
|
|
int sample_format = avio_r8(pb); // 0 = BE, 1 = LE
|
|
int sample_size = (avio_r8(pb) / 8 - 2); // 16, 24, 32
|
|
if (sample_format > 1 || sample_size > 2U || codec_config->audio_roll_distance)
|
|
return AVERROR_INVALIDDATA;
|
|
|
|
codec_config->codec_id = sample_fmt[sample_format][sample_size];
|
|
codec_config->sample_rate = avio_rb32(pb);
|
|
|
|
if (len - avio_tell(pb))
|
|
return AVERROR_INVALIDDATA;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int codec_config_obu(void *s, IAMFContext *c, AVIOContext *pb, int len)
|
|
{
|
|
IAMFCodecConfig **tmp, *codec_config = NULL;
|
|
AVIOContext pbc;
|
|
uint8_t *buf;
|
|
enum AVCodecID avcodec_id;
|
|
unsigned codec_config_id, nb_samples, codec_id;
|
|
int16_t audio_roll_distance;
|
|
int ret;
|
|
|
|
buf = av_malloc(len);
|
|
if (!buf)
|
|
return AVERROR(ENOMEM);
|
|
|
|
ret = ffio_read_size(pb, buf, len);
|
|
if (ret < 0)
|
|
goto fail;
|
|
|
|
ffio_init_context(&pbc, buf, len, 0, NULL, NULL, NULL, NULL);
|
|
|
|
codec_config_id = ffio_read_leb(&pbc);
|
|
codec_id = avio_rb32(&pbc);
|
|
nb_samples = ffio_read_leb(&pbc);
|
|
audio_roll_distance = avio_rb16(&pbc);
|
|
|
|
switch (codec_id) {
|
|
case MKBETAG('O','p','u','s'):
|
|
avcodec_id = AV_CODEC_ID_OPUS;
|
|
break;
|
|
case MKBETAG('m','p','4','a'):
|
|
avcodec_id = AV_CODEC_ID_AAC;
|
|
break;
|
|
case MKBETAG('f','L','a','C'):
|
|
avcodec_id = AV_CODEC_ID_FLAC;
|
|
break;
|
|
default:
|
|
avcodec_id = AV_CODEC_ID_NONE;
|
|
break;
|
|
}
|
|
|
|
for (int i = 0; i < c->nb_codec_configs; i++)
|
|
if (c->codec_configs[i]->codec_config_id == codec_config_id) {
|
|
ret = AVERROR_INVALIDDATA;
|
|
goto fail;
|
|
}
|
|
|
|
tmp = av_realloc_array(c->codec_configs, c->nb_codec_configs + 1, sizeof(*c->codec_configs));
|
|
if (!tmp) {
|
|
ret = AVERROR(ENOMEM);
|
|
goto fail;
|
|
}
|
|
c->codec_configs = tmp;
|
|
|
|
codec_config = av_mallocz(sizeof(*codec_config));
|
|
if (!codec_config) {
|
|
ret = AVERROR(ENOMEM);
|
|
goto fail;
|
|
}
|
|
|
|
codec_config->codec_config_id = codec_config_id;
|
|
codec_config->codec_id = avcodec_id;
|
|
codec_config->nb_samples = nb_samples;
|
|
codec_config->audio_roll_distance = audio_roll_distance;
|
|
|
|
switch (codec_id) {
|
|
case MKBETAG('O','p','u','s'):
|
|
ret = opus_decoder_config(codec_config, &pbc, len);
|
|
break;
|
|
case MKBETAG('m','p','4','a'):
|
|
ret = aac_decoder_config(codec_config, &pbc, len, s);
|
|
break;
|
|
case MKBETAG('f','L','a','C'):
|
|
ret = flac_decoder_config(codec_config, &pbc, len);
|
|
break;
|
|
case MKBETAG('i','p','c','m'):
|
|
ret = ipcm_decoder_config(codec_config, &pbc, len);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
if (ret < 0)
|
|
goto fail;
|
|
|
|
if ((codec_config->nb_samples > INT_MAX) || codec_config->nb_samples <= 0 ||
|
|
(-codec_config->audio_roll_distance > INT_MAX / codec_config->nb_samples)) {
|
|
ret = AVERROR_INVALIDDATA;
|
|
goto fail;
|
|
}
|
|
|
|
c->codec_configs[c->nb_codec_configs++] = codec_config;
|
|
|
|
len -= avio_tell(&pbc);
|
|
if (len)
|
|
av_log(s, AV_LOG_WARNING, "Underread in codec_config_obu. %d bytes left at the end\n", len);
|
|
|
|
ret = 0;
|
|
fail:
|
|
av_free(buf);
|
|
if (ret < 0) {
|
|
if (codec_config)
|
|
av_free(codec_config->extradata);
|
|
av_free(codec_config);
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
int ff_iamf_parse_obu_header(const uint8_t *buf, int buf_size,
|
|
unsigned *obu_size, int *start_pos, enum IAMF_OBU_Type *type,
|
|
unsigned *skip_samples, unsigned *discard_padding)
|
|
{
|
|
GetBitContext gb;
|
|
int ret, extension_flag, trimming, start;
|
|
unsigned skip = 0, discard = 0;
|
|
unsigned size;
|
|
|
|
ret = init_get_bits8(&gb, buf, FFMIN(buf_size, MAX_IAMF_OBU_HEADER_SIZE));
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
*type = get_bits(&gb, 5);
|
|
/*redundant =*/ get_bits1(&gb);
|
|
trimming = get_bits1(&gb);
|
|
extension_flag = get_bits1(&gb);
|
|
|
|
*obu_size = get_leb(&gb);
|
|
if (*obu_size > INT_MAX)
|
|
return AVERROR_INVALIDDATA;
|
|
|
|
start = get_bits_count(&gb) / 8;
|
|
|
|
if (trimming) {
|
|
discard = get_leb(&gb); // num_samples_to_trim_at_end
|
|
skip = get_leb(&gb); // num_samples_to_trim_at_start
|
|
}
|
|
|
|
if (skip_samples)
|
|
*skip_samples = skip;
|
|
if (discard_padding)
|
|
*discard_padding = discard;
|
|
|
|
if (extension_flag) {
|
|
unsigned int extension_bytes;
|
|
extension_bytes = get_leb(&gb);
|
|
if (extension_bytes > INT_MAX / 8)
|
|
return AVERROR_INVALIDDATA;
|
|
skip_bits_long(&gb, extension_bytes * 8);
|
|
}
|
|
|
|
if (get_bits_left(&gb) < 0)
|
|
return AVERROR_INVALIDDATA;
|
|
|
|
size = *obu_size + start;
|
|
if (size > INT_MAX)
|
|
return AVERROR_INVALIDDATA;
|
|
|
|
*obu_size -= get_bits_count(&gb) / 8 - start;
|
|
*start_pos = size - *obu_size;
|
|
|
|
return size;
|
|
}
|
|
|
|
int ff_iamfdec_read_descriptors(IAMFContext *c, AVIOContext *pb,
|
|
int max_size, void *log_ctx)
|
|
{
|
|
uint8_t header[MAX_IAMF_OBU_HEADER_SIZE + AV_INPUT_BUFFER_PADDING_SIZE];
|
|
int ret;
|
|
|
|
while (1) {
|
|
unsigned obu_size;
|
|
enum IAMF_OBU_Type type;
|
|
int start_pos, len, size;
|
|
|
|
if ((ret = ffio_ensure_seekback(pb, FFMIN(MAX_IAMF_OBU_HEADER_SIZE, max_size))) < 0)
|
|
return ret;
|
|
size = avio_read(pb, header, FFMIN(MAX_IAMF_OBU_HEADER_SIZE, max_size));
|
|
if (size < 0)
|
|
return size;
|
|
memset(header + size, 0, AV_INPUT_BUFFER_PADDING_SIZE);
|
|
|
|
len = ff_iamf_parse_obu_header(header, size, &obu_size, &start_pos, &type, NULL, NULL);
|
|
if (len < 0 || obu_size > max_size) {
|
|
av_log(log_ctx, AV_LOG_ERROR, "Failed to read obu header\n");
|
|
avio_seek(pb, -size, SEEK_CUR);
|
|
return len;
|
|
}
|
|
|
|
if (type >= IAMF_OBU_IA_PARAMETER_BLOCK && type < IAMF_OBU_IA_SEQUENCE_HEADER) {
|
|
avio_seek(pb, -size, SEEK_CUR);
|
|
break;
|
|
}
|
|
|
|
avio_seek(pb, -(size - start_pos), SEEK_CUR);
|
|
switch (type) {
|
|
case IAMF_OBU_IA_CODEC_CONFIG:
|
|
ret = codec_config_obu(log_ctx, c, pb, obu_size);
|
|
break;
|
|
default: {
|
|
int64_t offset = avio_skip(pb, obu_size);
|
|
if (offset < 0)
|
|
ret = offset;
|
|
break;
|
|
}
|
|
}
|
|
if (ret < 0) {
|
|
av_log(log_ctx, AV_LOG_ERROR, "Failed to read obu type %d\n", type);
|
|
return ret;
|
|
}
|
|
max_size -= obu_size + start_pos;
|
|
if (max_size < 0)
|
|
return AVERROR_INVALIDDATA;
|
|
if (!max_size)
|
|
break;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
void ff_iamf_uninit_context(IAMFContext *c)
|
|
{
|
|
if (!c)
|
|
return;
|
|
|
|
for (int i = 0; i < c->nb_codec_configs; i++) {
|
|
av_free(c->codec_configs[i]->extradata);
|
|
av_free(c->codec_configs[i]);
|
|
}
|
|
av_freep(&c->codec_configs);
|
|
c->nb_codec_configs = 0;
|
|
}
|
|
|