/* * LPCM codecs for PCM formats found in Video DVD streams * Copyright (c) 2018 Paul B Mahol * * 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/channel_layout.h" #include "avcodec.h" #include "bytestream.h" #include "codec_internal.h" #include "encode.h" typedef struct PCMDVDContext { uint8_t header[3]; // Header added to every frame int block_size; // Size of a block of samples in bytes int samples_per_block; // Number of samples per channel per block int groups_per_block; // Number of 20/24-bit sample groups per block } PCMDVDContext; static av_cold int pcm_dvd_encode_init(AVCodecContext *avctx) { PCMDVDContext *s = avctx->priv_data; int quant, freq, frame_size; switch (avctx->sample_rate) { case 48000: freq = 0; break; case 96000: freq = 1; break; default: av_assert1(0); } switch (avctx->sample_fmt) { case AV_SAMPLE_FMT_S16: avctx->bits_per_coded_sample = 16; quant = 0; break; case AV_SAMPLE_FMT_S32: avctx->bits_per_coded_sample = 24; quant = 2; break; default: av_assert1(0); } avctx->bits_per_coded_sample = 16 + quant * 4; avctx->block_align = avctx->ch_layout.nb_channels * avctx->bits_per_coded_sample / 8; avctx->bit_rate = avctx->block_align * 8LL * avctx->sample_rate; if (avctx->bit_rate > 9800000) { av_log(avctx, AV_LOG_ERROR, "Too big bitrate: reduce sample rate, bitdepth or channels.\n"); return AVERROR(EINVAL); } if (avctx->sample_fmt == AV_SAMPLE_FMT_S16) { s->samples_per_block = 1; s->block_size = avctx->ch_layout.nb_channels * 2; frame_size = 2008 / s->block_size; } else { switch (avctx->ch_layout.nb_channels) { case 1: case 2: case 4: /* one group has all the samples needed */ s->block_size = 4 * avctx->bits_per_coded_sample / 8; s->samples_per_block = 4 / avctx->ch_layout.nb_channels; s->groups_per_block = 1; break; case 8: /* two groups have all the samples needed */ s->block_size = 8 * avctx->bits_per_coded_sample / 8; s->samples_per_block = 1; s->groups_per_block = 2; break; default: /* need avctx->ch_layout.nb_channels groups */ s->block_size = 4 * avctx->ch_layout.nb_channels * avctx->bits_per_coded_sample / 8; s->samples_per_block = 4; s->groups_per_block = avctx->ch_layout.nb_channels; break; } frame_size = FFALIGN(2008 / s->block_size, s->samples_per_block); } s->header[0] = 0x0c; s->header[1] = (quant << 6) | (freq << 4) | (avctx->ch_layout.nb_channels - 1); s->header[2] = 0x80; if (!avctx->frame_size) avctx->frame_size = frame_size; return 0; } static int pcm_dvd_encode_frame(AVCodecContext *avctx, AVPacket *avpkt, const AVFrame *frame, int *got_packet_ptr) { PCMDVDContext *s = avctx->priv_data; int samples = frame->nb_samples * avctx->ch_layout.nb_channels; int64_t pkt_size = (frame->nb_samples / s->samples_per_block) * s->block_size + 3; int blocks = (pkt_size - 3) / s->block_size; const int16_t *src16; const int32_t *src32; PutByteContext pb; int ret; if ((ret = ff_get_encode_buffer(avctx, avpkt, pkt_size, 0)) < 0) return ret; memcpy(avpkt->data, s->header, 3); src16 = (const int16_t *)frame->data[0]; src32 = (const int32_t *)frame->data[0]; bytestream2_init_writer(&pb, avpkt->data + 3, avpkt->size - 3); switch (avctx->sample_fmt) { case AV_SAMPLE_FMT_S16: do { bytestream2_put_be16(&pb, *src16++); } while (--samples); break; case AV_SAMPLE_FMT_S32: if (avctx->ch_layout.nb_channels == 1) { do { for (int i = 2; i; i--) { bytestream2_put_be16(&pb, src32[0] >> 16); bytestream2_put_be16(&pb, src32[1] >> 16); bytestream2_put_byte(&pb, (uint8_t)((*src32++) >> 8)); bytestream2_put_byte(&pb, (uint8_t)((*src32++) >> 8)); } } while (--blocks); } else { do { for (int i = s->groups_per_block; i; i--) { bytestream2_put_be16(&pb, src32[0] >> 16); bytestream2_put_be16(&pb, src32[1] >> 16); bytestream2_put_be16(&pb, src32[2] >> 16); bytestream2_put_be16(&pb, src32[3] >> 16); bytestream2_put_byte(&pb, (uint8_t)((*src32++) >> 8)); bytestream2_put_byte(&pb, (uint8_t)((*src32++) >> 8)); bytestream2_put_byte(&pb, (uint8_t)((*src32++) >> 8)); bytestream2_put_byte(&pb, (uint8_t)((*src32++) >> 8)); } } while (--blocks); } break; } *got_packet_ptr = 1; return 0; } const FFCodec ff_pcm_dvd_encoder = { .p.name = "pcm_dvd", CODEC_LONG_NAME("PCM signed 16|20|24-bit big-endian for DVD media"), .p.type = AVMEDIA_TYPE_AUDIO, .p.id = AV_CODEC_ID_PCM_DVD, .p.capabilities = AV_CODEC_CAP_DR1 | AV_CODEC_CAP_SMALL_LAST_FRAME | AV_CODEC_CAP_ENCODER_REORDERED_OPAQUE, .priv_data_size = sizeof(PCMDVDContext), .init = pcm_dvd_encode_init, FF_CODEC_ENCODE_CB(pcm_dvd_encode_frame), .p.supported_samplerates = (const int[]) { 48000, 96000, 0}, CODEC_OLD_CHANNEL_LAYOUTS(AV_CH_LAYOUT_MONO, AV_CH_LAYOUT_STEREO, AV_CH_LAYOUT_5POINT1, AV_CH_LAYOUT_7POINT1) .p.ch_layouts = (const AVChannelLayout[]) { AV_CHANNEL_LAYOUT_MONO, AV_CHANNEL_LAYOUT_STEREO, AV_CHANNEL_LAYOUT_5POINT1, AV_CHANNEL_LAYOUT_7POINT1, { 0 } }, .p.sample_fmts = (const enum AVSampleFormat[]){ AV_SAMPLE_FMT_S16, AV_SAMPLE_FMT_S32, AV_SAMPLE_FMT_NONE }, };