More exactly: Not more than one stream of each type for which a default codec (i.e. AVOutputFormat.(audio|video|subtitle)_codec) is set; for those types for which no such codec is set (or for which no designated default codec in AVOutputFormat exists at all) no streams are permitted. Given that with this flag set the default codecs become more important, they are now set explicitly to AV_CODEC_ID_NONE for "unset"; the earlier code relied on AV_CODEC_ID_NONE being equal to zero, so that default static initialization set it accordingly; but this is not how one is supposed to use an enum. Signed-off-by: Andreas Rheinhardt <andreas.rheinhardt@outlook.com>
179 lines
5.2 KiB
C
179 lines
5.2 KiB
C
/*
|
|
* True Audio (TTA) muxer
|
|
* Copyright (c) 2016 James Almer
|
|
*
|
|
* 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/crc.h"
|
|
#include "libavutil/intreadwrite.h"
|
|
|
|
#include "libavcodec/packet_internal.h"
|
|
#include "apetag.h"
|
|
#include "avformat.h"
|
|
#include "avio_internal.h"
|
|
#include "internal.h"
|
|
#include "mux.h"
|
|
|
|
typedef struct TTAMuxContext {
|
|
AVIOContext *seek_table;
|
|
PacketList queue;
|
|
uint32_t nb_samples;
|
|
int frame_size;
|
|
int last_frame;
|
|
} TTAMuxContext;
|
|
|
|
static int tta_init(AVFormatContext *s)
|
|
{
|
|
TTAMuxContext *tta = s->priv_data;
|
|
AVCodecParameters *par = s->streams[0]->codecpar;
|
|
|
|
if (par->codec_id != AV_CODEC_ID_TTA) {
|
|
av_log(s, AV_LOG_ERROR, "Unsupported codec\n");
|
|
return AVERROR(EINVAL);
|
|
}
|
|
if (par->extradata && par->extradata_size < 22) {
|
|
av_log(s, AV_LOG_ERROR, "Invalid TTA extradata\n");
|
|
return AVERROR_INVALIDDATA;
|
|
}
|
|
|
|
/* Prevent overflow */
|
|
if (par->sample_rate > 0x7FFFFFu) {
|
|
av_log(s, AV_LOG_ERROR, "Sample rate too large\n");
|
|
return AVERROR(EINVAL);
|
|
}
|
|
tta->frame_size = par->sample_rate * 256 / 245;
|
|
avpriv_set_pts_info(s->streams[0], 64, 1, par->sample_rate);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int tta_write_header(AVFormatContext *s)
|
|
{
|
|
TTAMuxContext *tta = s->priv_data;
|
|
AVCodecParameters *par = s->streams[0]->codecpar;
|
|
int ret;
|
|
|
|
if ((ret = avio_open_dyn_buf(&tta->seek_table)) < 0)
|
|
return ret;
|
|
|
|
/* Ignore most extradata information if present. It can be innacurate
|
|
if for example remuxing from Matroska */
|
|
ffio_init_checksum(s->pb, ff_crcEDB88320_update, UINT32_MAX);
|
|
ffio_init_checksum(tta->seek_table, ff_crcEDB88320_update, UINT32_MAX);
|
|
avio_write(s->pb, "TTA1", 4);
|
|
avio_wl16(s->pb, par->extradata ? AV_RL16(par->extradata + 4) : 1);
|
|
avio_wl16(s->pb, par->ch_layout.nb_channels);
|
|
avio_wl16(s->pb, par->bits_per_raw_sample);
|
|
avio_wl32(s->pb, par->sample_rate);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int tta_write_packet(AVFormatContext *s, AVPacket *pkt)
|
|
{
|
|
TTAMuxContext *tta = s->priv_data;
|
|
int ret;
|
|
|
|
ret = avpriv_packet_list_put(&tta->queue, pkt, NULL, 0);
|
|
if (ret < 0) {
|
|
return ret;
|
|
}
|
|
pkt = &tta->queue.tail->pkt;
|
|
|
|
avio_wl32(tta->seek_table, pkt->size);
|
|
tta->nb_samples += pkt->duration;
|
|
|
|
if (tta->frame_size != pkt->duration) {
|
|
if (tta->last_frame) {
|
|
/* Two frames with a different duration than the default frame
|
|
size means the TTA stream comes from a faulty container, and
|
|
there's no way the last frame duration will be correct. */
|
|
av_log(s, AV_LOG_ERROR, "Invalid frame durations\n");
|
|
|
|
return AVERROR_INVALIDDATA;
|
|
}
|
|
/* First frame with a different duration than the default frame size.
|
|
Assume it's the last frame in the stream and continue. */
|
|
tta->last_frame++;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void tta_queue_flush(AVFormatContext *s)
|
|
{
|
|
TTAMuxContext *tta = s->priv_data;
|
|
AVPacket *const pkt = ffformatcontext(s)->pkt;
|
|
|
|
while (tta->queue.head) {
|
|
avpriv_packet_list_get(&tta->queue, pkt);
|
|
avio_write(s->pb, pkt->data, pkt->size);
|
|
av_packet_unref(pkt);
|
|
}
|
|
}
|
|
|
|
static int tta_write_trailer(AVFormatContext *s)
|
|
{
|
|
TTAMuxContext *tta = s->priv_data;
|
|
uint8_t *ptr;
|
|
unsigned int crc;
|
|
int size;
|
|
|
|
avio_wl32(s->pb, tta->nb_samples);
|
|
crc = ffio_get_checksum(s->pb) ^ UINT32_MAX;
|
|
avio_wl32(s->pb, crc);
|
|
|
|
/* Write Seek table */
|
|
crc = ffio_get_checksum(tta->seek_table) ^ UINT32_MAX;
|
|
avio_wl32(tta->seek_table, crc);
|
|
size = avio_get_dyn_buf(tta->seek_table, &ptr);
|
|
avio_write(s->pb, ptr, size);
|
|
|
|
/* Write audio data */
|
|
tta_queue_flush(s);
|
|
|
|
ff_ape_write_tag(s);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void tta_deinit(AVFormatContext *s)
|
|
{
|
|
TTAMuxContext *tta = s->priv_data;
|
|
|
|
ffio_free_dyn_buf(&tta->seek_table);
|
|
avpriv_packet_list_free(&tta->queue);
|
|
}
|
|
|
|
const FFOutputFormat ff_tta_muxer = {
|
|
.p.name = "tta",
|
|
.p.long_name = NULL_IF_CONFIG_SMALL("TTA (True Audio)"),
|
|
.p.mime_type = "audio/x-tta",
|
|
.p.extensions = "tta",
|
|
.priv_data_size = sizeof(TTAMuxContext),
|
|
.p.audio_codec = AV_CODEC_ID_TTA,
|
|
.p.video_codec = AV_CODEC_ID_NONE,
|
|
.p.subtitle_codec = AV_CODEC_ID_NONE,
|
|
.flags_internal = FF_OFMT_FLAG_MAX_ONE_OF_EACH,
|
|
.init = tta_init,
|
|
.deinit = tta_deinit,
|
|
.write_header = tta_write_header,
|
|
.write_packet = tta_write_packet,
|
|
.write_trailer = tta_write_trailer,
|
|
};
|