...
This commit is contained in:
@@ -0,0 +1,53 @@
|
||||
clean::
|
||||
$(RM) $(CLEANSUFFIXES:%=libavcodec/bsf/%)
|
||||
|
||||
OBJS-$(CONFIG_AAC_ADTSTOASC_BSF) += bsf/aac_adtstoasc.o
|
||||
OBJS-$(CONFIG_APV_METADATA_BSF) += bsf/apv_metadata.o
|
||||
OBJS-$(CONFIG_AV1_FRAME_MERGE_BSF) += bsf/av1_frame_merge.o
|
||||
OBJS-$(CONFIG_AV1_FRAME_SPLIT_BSF) += bsf/av1_frame_split.o
|
||||
OBJS-$(CONFIG_AV1_METADATA_BSF) += bsf/av1_metadata.o
|
||||
OBJS-$(CONFIG_CHOMP_BSF) += bsf/chomp.o
|
||||
OBJS-$(CONFIG_DCA_CORE_BSF) += bsf/dca_core.o
|
||||
OBJS-$(CONFIG_DTS2PTS_BSF) += bsf/dts2pts.o
|
||||
OBJS-$(CONFIG_DUMP_EXTRADATA_BSF) += bsf/dump_extradata.o
|
||||
OBJS-$(CONFIG_DV_ERROR_MARKER_BSF) += bsf/dv_error_marker.o
|
||||
OBJS-$(CONFIG_EAC3_CORE_BSF) += bsf/eac3_core.o
|
||||
OBJS-$(CONFIG_EIA608_TO_SMPTE436M_BSF) += bsf/eia608_to_smpte436m.o
|
||||
OBJS-$(CONFIG_EVC_FRAME_MERGE_BSF) += bsf/evc_frame_merge.o
|
||||
OBJS-$(CONFIG_EXTRACT_EXTRADATA_BSF) += bsf/extract_extradata.o
|
||||
OBJS-$(CONFIG_FILTER_UNITS_BSF) += bsf/filter_units.o
|
||||
OBJS-$(CONFIG_H264_METADATA_BSF) += bsf/h264_metadata.o
|
||||
OBJS-$(CONFIG_H264_MP4TOANNEXB_BSF) += bsf/h264_mp4toannexb.o
|
||||
OBJS-$(CONFIG_H264_REDUNDANT_PPS_BSF) += bsf/h264_redundant_pps.o
|
||||
OBJS-$(CONFIG_HAPQA_EXTRACT_BSF) += bsf/hapqa_extract.o
|
||||
OBJS-$(CONFIG_HEVC_METADATA_BSF) += bsf/h265_metadata.o
|
||||
OBJS-$(CONFIG_DOVI_RPU_BSF) += bsf/dovi_rpu.o
|
||||
OBJS-$(CONFIG_HEVC_MP4TOANNEXB_BSF) += bsf/hevc_mp4toannexb.o
|
||||
OBJS-$(CONFIG_IMX_DUMP_HEADER_BSF) += bsf/imx_dump_header.o
|
||||
OBJS-$(CONFIG_MEDIA100_TO_MJPEGB_BSF) += bsf/media100_to_mjpegb.o
|
||||
OBJS-$(CONFIG_MJPEG2JPEG_BSF) += bsf/mjpeg2jpeg.o
|
||||
OBJS-$(CONFIG_MJPEGA_DUMP_HEADER_BSF) += bsf/mjpega_dump_header.o
|
||||
OBJS-$(CONFIG_MOV2TEXTSUB_BSF) += bsf/movsub.o
|
||||
OBJS-$(CONFIG_MPEG2_METADATA_BSF) += bsf/mpeg2_metadata.o
|
||||
OBJS-$(CONFIG_MPEG4_UNPACK_BFRAMES_BSF) += bsf/mpeg4_unpack_bframes.o
|
||||
OBJS-$(CONFIG_NOISE_BSF) += bsf/noise.o
|
||||
OBJS-$(CONFIG_NULL_BSF) += bsf/null.o
|
||||
OBJS-$(CONFIG_OPUS_METADATA_BSF) += bsf/opus_metadata.o
|
||||
OBJS-$(CONFIG_PCM_RECHUNK_BSF) += bsf/pcm_rechunk.o
|
||||
OBJS-$(CONFIG_PGS_FRAME_MERGE_BSF) += bsf/pgs_frame_merge.o
|
||||
OBJS-$(CONFIG_PRORES_METADATA_BSF) += bsf/prores_metadata.o
|
||||
OBJS-$(CONFIG_REMOVE_EXTRADATA_BSF) += bsf/remove_extradata.o
|
||||
OBJS-$(CONFIG_SETTS_BSF) += bsf/setts.o
|
||||
OBJS-$(CONFIG_SHOWINFO_BSF) += bsf/showinfo.o
|
||||
OBJS-$(CONFIG_SMPTE436M_TO_EIA608_BSF) += bsf/smpte436m_to_eia608.o
|
||||
OBJS-$(CONFIG_TEXT2MOVSUB_BSF) += bsf/movsub.o
|
||||
OBJS-$(CONFIG_TRACE_HEADERS_BSF) += bsf/trace_headers.o
|
||||
OBJS-$(CONFIG_TRUEHD_CORE_BSF) += bsf/truehd_core.o
|
||||
OBJS-$(CONFIG_VP9_METADATA_BSF) += bsf/vp9_metadata.o
|
||||
OBJS-$(CONFIG_VP9_RAW_REORDER_BSF) += bsf/vp9_raw_reorder.o
|
||||
OBJS-$(CONFIG_VP9_SUPERFRAME_BSF) += bsf/vp9_superframe.o
|
||||
OBJS-$(CONFIG_VP9_SUPERFRAME_SPLIT_BSF) += bsf/vp9_superframe_split.o
|
||||
OBJS-$(CONFIG_VVC_METADATA_BSF) += bsf/h266_metadata.o
|
||||
OBJS-$(CONFIG_VVC_MP4TOANNEXB_BSF) += bsf/vvc_mp4toannexb.o
|
||||
|
||||
libavcodec/bsf/%.o: CPPFLAGS += -I$(SRC_PATH)/libavcodec/
|
||||
@@ -0,0 +1,156 @@
|
||||
/*
|
||||
* MPEG-2/4 AAC ADTS to MPEG-4 Audio Specific Configuration bitstream filter
|
||||
* Copyright (c) 2009 Alex Converse <alex.converse@gmail.com>
|
||||
*
|
||||
* 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 "adts_header.h"
|
||||
#include "adts_parser.h"
|
||||
#include "bsf.h"
|
||||
#include "bsf_internal.h"
|
||||
#include "put_bits.h"
|
||||
#include "get_bits.h"
|
||||
#include "mpeg4audio.h"
|
||||
#include "mpeg4audio_copy_pce.h"
|
||||
|
||||
typedef struct AACBSFContext {
|
||||
int first_frame_done;
|
||||
} AACBSFContext;
|
||||
|
||||
/**
|
||||
* This filter creates an MPEG-4 AudioSpecificConfig from an MPEG-2/4
|
||||
* ADTS header and removes the ADTS header.
|
||||
*/
|
||||
static int aac_adtstoasc_filter(AVBSFContext *bsfc, AVPacket *pkt)
|
||||
{
|
||||
AACBSFContext *ctx = bsfc->priv_data;
|
||||
|
||||
PutBitContext pb;
|
||||
AACADTSHeaderInfo hdr;
|
||||
int ret;
|
||||
|
||||
ret = ff_bsf_get_packet_ref(bsfc, pkt);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
if (bsfc->par_in->extradata && pkt->size >= 2 && (AV_RB16(pkt->data) >> 4) != 0xfff)
|
||||
return 0;
|
||||
|
||||
if (pkt->size < AV_AAC_ADTS_HEADER_SIZE)
|
||||
goto packet_too_small;
|
||||
|
||||
if (ff_adts_header_parse_buf(pkt->data, &hdr) < 0) {
|
||||
av_log(bsfc, AV_LOG_ERROR, "Error parsing ADTS frame header!\n");
|
||||
ret = AVERROR_INVALIDDATA;
|
||||
goto fail;
|
||||
}
|
||||
|
||||
if (!hdr.crc_absent && hdr.num_aac_frames > 1) {
|
||||
avpriv_report_missing_feature(bsfc,
|
||||
"Multiple RDBs per frame with CRC");
|
||||
ret = AVERROR_PATCHWELCOME;
|
||||
goto fail;
|
||||
}
|
||||
|
||||
pkt->size -= AV_AAC_ADTS_HEADER_SIZE + 2 * !hdr.crc_absent;
|
||||
if (pkt->size <= 0)
|
||||
goto packet_too_small;
|
||||
pkt->data += AV_AAC_ADTS_HEADER_SIZE + 2 * !hdr.crc_absent;
|
||||
|
||||
if (!ctx->first_frame_done) {
|
||||
int pce_size = 0;
|
||||
uint8_t pce_data[MAX_PCE_SIZE];
|
||||
uint8_t *extradata;
|
||||
|
||||
if (!hdr.chan_config) {
|
||||
GetBitContext gb;
|
||||
init_get_bits(&gb, pkt->data, pkt->size * 8);
|
||||
if (get_bits(&gb, 3) != 5) {
|
||||
avpriv_report_missing_feature(bsfc,
|
||||
"PCE-based channel configuration "
|
||||
"without PCE as first syntax "
|
||||
"element");
|
||||
ret = AVERROR_PATCHWELCOME;
|
||||
goto fail;
|
||||
}
|
||||
init_put_bits(&pb, pce_data, MAX_PCE_SIZE);
|
||||
pce_size = ff_copy_pce_data(&pb, &gb) / 8;
|
||||
flush_put_bits(&pb);
|
||||
pkt->size -= get_bits_count(&gb)/8;
|
||||
pkt->data += get_bits_count(&gb)/8;
|
||||
}
|
||||
|
||||
extradata = av_packet_new_side_data(pkt, AV_PKT_DATA_NEW_EXTRADATA,
|
||||
2 + pce_size);
|
||||
if (!extradata) {
|
||||
ret = AVERROR(ENOMEM);
|
||||
goto fail;
|
||||
}
|
||||
|
||||
init_put_bits(&pb, extradata, 2 + pce_size);
|
||||
put_bits(&pb, 5, hdr.object_type);
|
||||
put_bits(&pb, 4, hdr.sampling_index);
|
||||
put_bits(&pb, 4, hdr.chan_config);
|
||||
put_bits(&pb, 1, 0); //frame length - 1024 samples
|
||||
put_bits(&pb, 1, 0); //does not depend on core coder
|
||||
put_bits(&pb, 1, 0); //is not extension
|
||||
flush_put_bits(&pb);
|
||||
if (pce_size) {
|
||||
memcpy(extradata + 2, pce_data, pce_size);
|
||||
}
|
||||
|
||||
ctx->first_frame_done = 1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
packet_too_small:
|
||||
av_log(bsfc, AV_LOG_ERROR, "Input packet too small\n");
|
||||
ret = AVERROR_INVALIDDATA;
|
||||
fail:
|
||||
av_packet_unref(pkt);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int aac_adtstoasc_init(AVBSFContext *ctx)
|
||||
{
|
||||
/* Validate the extradata if the stream is already MPEG-4 AudioSpecificConfig */
|
||||
if (ctx->par_in->extradata) {
|
||||
MPEG4AudioConfig mp4ac;
|
||||
int ret = avpriv_mpeg4audio_get_config2(&mp4ac, ctx->par_in->extradata,
|
||||
ctx->par_in->extradata_size, 1, ctx);
|
||||
if (ret < 0) {
|
||||
av_log(ctx, AV_LOG_ERROR, "Error parsing AudioSpecificConfig extradata!\n");
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const enum AVCodecID codec_ids[] = {
|
||||
AV_CODEC_ID_AAC, AV_CODEC_ID_NONE,
|
||||
};
|
||||
|
||||
const FFBitStreamFilter ff_aac_adtstoasc_bsf = {
|
||||
.p.name = "aac_adtstoasc",
|
||||
.p.codec_ids = codec_ids,
|
||||
.priv_data_size = sizeof(AACBSFContext),
|
||||
.init = aac_adtstoasc_init,
|
||||
.filter = aac_adtstoasc_filter,
|
||||
};
|
||||
@@ -0,0 +1,134 @@
|
||||
/*
|
||||
* 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/common.h"
|
||||
#include "libavutil/opt.h"
|
||||
|
||||
#include "bsf.h"
|
||||
#include "bsf_internal.h"
|
||||
#include "cbs.h"
|
||||
#include "cbs_bsf.h"
|
||||
#include "cbs_apv.h"
|
||||
|
||||
typedef struct APVMetadataContext {
|
||||
CBSBSFContext common;
|
||||
|
||||
int color_primaries;
|
||||
int transfer_characteristics;
|
||||
int matrix_coefficients;
|
||||
int full_range_flag;
|
||||
} APVMetadataContext;
|
||||
|
||||
|
||||
static int apv_metadata_update_frame_header(AVBSFContext *bsf,
|
||||
APVRawFrameHeader *hdr)
|
||||
{
|
||||
APVMetadataContext *ctx = bsf->priv_data;
|
||||
|
||||
if (ctx->color_primaries >= 0 ||
|
||||
ctx->transfer_characteristics >= 0 ||
|
||||
ctx->matrix_coefficients >= 0 ||
|
||||
ctx->full_range_flag >= 0) {
|
||||
hdr->color_description_present_flag = 1;
|
||||
|
||||
if (ctx->color_primaries >= 0)
|
||||
hdr->color_primaries = ctx->color_primaries;
|
||||
if (ctx->transfer_characteristics >= 0)
|
||||
hdr->transfer_characteristics = ctx->transfer_characteristics;
|
||||
if (ctx->matrix_coefficients >= 0)
|
||||
hdr->matrix_coefficients = ctx->matrix_coefficients;
|
||||
if (ctx->full_range_flag >= 0)
|
||||
hdr->full_range_flag = ctx->full_range_flag;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int apv_metadata_update_fragment(AVBSFContext *bsf, AVPacket *pkt,
|
||||
CodedBitstreamFragment *frag)
|
||||
{
|
||||
int err, i;
|
||||
|
||||
for (i = 0; i < frag->nb_units; i++) {
|
||||
if (frag->units[i].type == APV_PBU_PRIMARY_FRAME) {
|
||||
APVRawFrame *pbu = frag->units[i].content;
|
||||
err = apv_metadata_update_frame_header(bsf, &pbu->frame_header);
|
||||
if (err < 0)
|
||||
return err;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const CBSBSFType apv_metadata_type = {
|
||||
.codec_id = AV_CODEC_ID_APV,
|
||||
.fragment_name = "access unit",
|
||||
.unit_name = "PBU",
|
||||
.update_fragment = &apv_metadata_update_fragment,
|
||||
};
|
||||
|
||||
static int apv_metadata_init(AVBSFContext *bsf)
|
||||
{
|
||||
return ff_cbs_bsf_generic_init(bsf, &apv_metadata_type);
|
||||
}
|
||||
|
||||
#define OFFSET(x) offsetof(APVMetadataContext, x)
|
||||
#define FLAGS (AV_OPT_FLAG_VIDEO_PARAM|AV_OPT_FLAG_BSF_PARAM)
|
||||
static const AVOption apv_metadata_options[] = {
|
||||
{ "color_primaries", "Set color primaries (section 5.3.5)",
|
||||
OFFSET(color_primaries), AV_OPT_TYPE_INT,
|
||||
{ .i64 = -1 }, -1, 255, FLAGS },
|
||||
{ "transfer_characteristics", "Set transfer characteristics (section 5.3.5)",
|
||||
OFFSET(transfer_characteristics), AV_OPT_TYPE_INT,
|
||||
{ .i64 = -1 }, -1, 255, FLAGS },
|
||||
{ "matrix_coefficients", "Set matrix coefficients (section 5.3.5)",
|
||||
OFFSET(matrix_coefficients), AV_OPT_TYPE_INT,
|
||||
{ .i64 = -1 }, -1, 255, FLAGS },
|
||||
|
||||
{ "full_range_flag", "Set full range flag flag (section 5.3.5)",
|
||||
OFFSET(full_range_flag), AV_OPT_TYPE_INT,
|
||||
{ .i64 = -1 }, -1, 1, FLAGS, .unit = "cr" },
|
||||
{ "tv", "TV (limited) range", 0, AV_OPT_TYPE_CONST,
|
||||
{ .i64 = 0 }, .flags = FLAGS, .unit = "cr" },
|
||||
{ "pc", "PC (full) range", 0, AV_OPT_TYPE_CONST,
|
||||
{ .i64 = 1 }, .flags = FLAGS, .unit = "cr" },
|
||||
|
||||
{ NULL }
|
||||
};
|
||||
|
||||
static const AVClass apv_metadata_class = {
|
||||
.class_name = "apv_metadata_bsf",
|
||||
.item_name = av_default_item_name,
|
||||
.option = apv_metadata_options,
|
||||
.version = LIBAVUTIL_VERSION_INT,
|
||||
};
|
||||
|
||||
static const enum AVCodecID apv_metadata_codec_ids[] = {
|
||||
AV_CODEC_ID_APV, AV_CODEC_ID_NONE,
|
||||
};
|
||||
|
||||
const FFBitStreamFilter ff_apv_metadata_bsf = {
|
||||
.p.name = "apv_metadata",
|
||||
.p.codec_ids = apv_metadata_codec_ids,
|
||||
.p.priv_class = &apv_metadata_class,
|
||||
.priv_data_size = sizeof(APVMetadataContext),
|
||||
.init = &apv_metadata_init,
|
||||
.close = &ff_cbs_bsf_generic_close,
|
||||
.filter = &ff_cbs_bsf_generic_filter,
|
||||
};
|
||||
@@ -0,0 +1,167 @@
|
||||
/*
|
||||
* Copyright (c) 2019 James Almer <jamrial@gmail.com>
|
||||
*
|
||||
* 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 "bsf.h"
|
||||
#include "bsf_internal.h"
|
||||
#include "cbs.h"
|
||||
#include "cbs_av1.h"
|
||||
|
||||
typedef struct AV1FMergeContext {
|
||||
CodedBitstreamContext *input;
|
||||
CodedBitstreamContext *output;
|
||||
CodedBitstreamFragment frag[2];
|
||||
AVPacket *pkt, *in;
|
||||
int idx;
|
||||
} AV1FMergeContext;
|
||||
|
||||
static void av1_frame_merge_flush(AVBSFContext *bsf)
|
||||
{
|
||||
AV1FMergeContext *ctx = bsf->priv_data;
|
||||
|
||||
ff_cbs_fragment_reset(&ctx->frag[0]);
|
||||
ff_cbs_fragment_reset(&ctx->frag[1]);
|
||||
av_packet_unref(ctx->in);
|
||||
av_packet_unref(ctx->pkt);
|
||||
}
|
||||
|
||||
static int av1_frame_merge_filter(AVBSFContext *bsf, AVPacket *out)
|
||||
{
|
||||
AV1FMergeContext *ctx = bsf->priv_data;
|
||||
CodedBitstreamFragment *frag = &ctx->frag[ctx->idx], *tu = &ctx->frag[!ctx->idx];
|
||||
AVPacket *in = ctx->in, *buffer_pkt = ctx->pkt;
|
||||
int err, i;
|
||||
|
||||
err = ff_bsf_get_packet_ref(bsf, in);
|
||||
if (err < 0) {
|
||||
if (err == AVERROR_EOF && tu->nb_units > 0)
|
||||
goto eof;
|
||||
return err;
|
||||
}
|
||||
|
||||
err = ff_cbs_read_packet(ctx->input, frag, in);
|
||||
if (err < 0) {
|
||||
av_log(bsf, AV_LOG_ERROR, "Failed to read packet.\n");
|
||||
goto fail;
|
||||
}
|
||||
|
||||
if (frag->nb_units == 0) {
|
||||
av_log(bsf, AV_LOG_ERROR, "No OBU in packet.\n");
|
||||
err = AVERROR_INVALIDDATA;
|
||||
goto fail;
|
||||
}
|
||||
|
||||
if (tu->nb_units == 0 && frag->units[0].type != AV1_OBU_TEMPORAL_DELIMITER) {
|
||||
av_log(bsf, AV_LOG_ERROR, "Missing Temporal Delimiter.\n");
|
||||
err = AVERROR_INVALIDDATA;
|
||||
goto fail;
|
||||
}
|
||||
|
||||
for (i = 1; i < frag->nb_units; i++) {
|
||||
if (frag->units[i].type == AV1_OBU_TEMPORAL_DELIMITER) {
|
||||
av_log(bsf, AV_LOG_ERROR, "Temporal Delimiter in the middle of a packet.\n");
|
||||
err = AVERROR_INVALIDDATA;
|
||||
goto fail;
|
||||
}
|
||||
}
|
||||
|
||||
if (tu->nb_units > 0 && frag->units[0].type == AV1_OBU_TEMPORAL_DELIMITER) {
|
||||
eof:
|
||||
err = ff_cbs_write_packet(ctx->output, buffer_pkt, tu);
|
||||
if (err < 0) {
|
||||
av_log(bsf, AV_LOG_ERROR, "Failed to write packet.\n");
|
||||
goto fail;
|
||||
}
|
||||
av_packet_move_ref(out, buffer_pkt);
|
||||
|
||||
// Swap fragment index, to avoid copying fragment references.
|
||||
ctx->idx = !ctx->idx;
|
||||
} else {
|
||||
for (i = 0; i < frag->nb_units; i++) {
|
||||
err = ff_cbs_insert_unit_content(tu, -1, frag->units[i].type,
|
||||
frag->units[i].content, frag->units[i].content_ref);
|
||||
if (err < 0)
|
||||
goto fail;
|
||||
}
|
||||
|
||||
err = AVERROR(EAGAIN);
|
||||
}
|
||||
|
||||
/* Buffer packets with timestamps (there should be at most one per TU)
|
||||
* or any packet if buffer_pkt is empty. The latter is needed to
|
||||
* passthrough positions in case there are no timestamps like with
|
||||
* the raw OBU demuxer. */
|
||||
if (!buffer_pkt->data ||
|
||||
in->pts != AV_NOPTS_VALUE && buffer_pkt->pts == AV_NOPTS_VALUE) {
|
||||
av_packet_unref(buffer_pkt);
|
||||
av_packet_move_ref(buffer_pkt, in);
|
||||
} else
|
||||
av_packet_unref(in);
|
||||
|
||||
ff_cbs_fragment_reset(&ctx->frag[ctx->idx]);
|
||||
|
||||
fail:
|
||||
if (err < 0 && err != AVERROR(EAGAIN))
|
||||
av1_frame_merge_flush(bsf);
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
static int av1_frame_merge_init(AVBSFContext *bsf)
|
||||
{
|
||||
AV1FMergeContext *ctx = bsf->priv_data;
|
||||
int err;
|
||||
|
||||
ctx->in = av_packet_alloc();
|
||||
ctx->pkt = av_packet_alloc();
|
||||
if (!ctx->in || !ctx->pkt)
|
||||
return AVERROR(ENOMEM);
|
||||
|
||||
err = ff_cbs_init(&ctx->input, AV_CODEC_ID_AV1, bsf);
|
||||
if (err < 0)
|
||||
return err;
|
||||
|
||||
return ff_cbs_init(&ctx->output, AV_CODEC_ID_AV1, bsf);
|
||||
}
|
||||
|
||||
static void av1_frame_merge_close(AVBSFContext *bsf)
|
||||
{
|
||||
AV1FMergeContext *ctx = bsf->priv_data;
|
||||
|
||||
ff_cbs_fragment_free(&ctx->frag[0]);
|
||||
ff_cbs_fragment_free(&ctx->frag[1]);
|
||||
av_packet_free(&ctx->in);
|
||||
av_packet_free(&ctx->pkt);
|
||||
ff_cbs_close(&ctx->input);
|
||||
ff_cbs_close(&ctx->output);
|
||||
}
|
||||
|
||||
static const enum AVCodecID av1_frame_merge_codec_ids[] = {
|
||||
AV_CODEC_ID_AV1, AV_CODEC_ID_NONE,
|
||||
};
|
||||
|
||||
const FFBitStreamFilter ff_av1_frame_merge_bsf = {
|
||||
.p.name = "av1_frame_merge",
|
||||
.p.codec_ids = av1_frame_merge_codec_ids,
|
||||
.priv_data_size = sizeof(AV1FMergeContext),
|
||||
.init = av1_frame_merge_init,
|
||||
.flush = av1_frame_merge_flush,
|
||||
.close = av1_frame_merge_close,
|
||||
.filter = av1_frame_merge_filter,
|
||||
};
|
||||
@@ -0,0 +1,261 @@
|
||||
/*
|
||||
* Copyright (c) 2019 James Almer <jamrial@gmail.com>
|
||||
*
|
||||
* 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
|
||||
*/
|
||||
|
||||
/**
|
||||
* @file
|
||||
* This bitstream filter splits AV1 Temporal Units into packets containing
|
||||
* just one frame, plus any leading and trailing OBUs that may be present at
|
||||
* the beginning or end, respectively.
|
||||
*
|
||||
* Temporal Units already containing only one frame will be passed through
|
||||
* unchanged. When splitting can't be performed, the Temporal Unit will be
|
||||
* passed through containing only the remaining OBUs starting from the first
|
||||
* one after the last successfully split frame.
|
||||
*/
|
||||
|
||||
#include "libavutil/avassert.h"
|
||||
|
||||
#include "bsf.h"
|
||||
#include "bsf_internal.h"
|
||||
#include "cbs.h"
|
||||
#include "cbs_av1.h"
|
||||
|
||||
typedef struct AV1FSplitContext {
|
||||
AVPacket *buffer_pkt;
|
||||
CodedBitstreamContext *cbc;
|
||||
CodedBitstreamFragment temporal_unit;
|
||||
|
||||
int nb_frames;
|
||||
int cur_frame;
|
||||
int cur_frame_idx;
|
||||
int last_frame_idx;
|
||||
} AV1FSplitContext;
|
||||
|
||||
static int av1_frame_split_filter(AVBSFContext *ctx, AVPacket *out)
|
||||
{
|
||||
AV1FSplitContext *s = ctx->priv_data;
|
||||
CodedBitstreamFragment *td = &s->temporal_unit;
|
||||
int i, ret;
|
||||
int split = !!s->buffer_pkt->data;
|
||||
|
||||
if (!s->buffer_pkt->data) {
|
||||
int nb_frames = 0;
|
||||
|
||||
ret = ff_bsf_get_packet_ref(ctx, s->buffer_pkt);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
ret = ff_cbs_read_packet(s->cbc, td, s->buffer_pkt);
|
||||
if (ret < 0) {
|
||||
av_log(ctx, AV_LOG_WARNING, "Failed to parse temporal unit.\n");
|
||||
goto passthrough;
|
||||
}
|
||||
|
||||
for (i = 0; i < td->nb_units; i++) {
|
||||
CodedBitstreamUnit *unit = &td->units[i];
|
||||
|
||||
if (unit->type == AV1_OBU_FRAME ||
|
||||
unit->type == AV1_OBU_FRAME_HEADER)
|
||||
nb_frames++;
|
||||
else if (unit->type == AV1_OBU_TILE_LIST) {
|
||||
av_log(ctx, AV_LOG_VERBOSE, "Large scale tiles are unsupported.\n");
|
||||
goto passthrough;
|
||||
}
|
||||
}
|
||||
if (nb_frames > 1) {
|
||||
s->cur_frame = 0;
|
||||
s->cur_frame_idx = s->last_frame_idx = 0;
|
||||
s->nb_frames = nb_frames;
|
||||
split = 1;
|
||||
}
|
||||
}
|
||||
|
||||
if (split) {
|
||||
AV1RawFrameHeader *frame = NULL;
|
||||
int cur_frame_type = -1, size = 0;
|
||||
|
||||
for (i = s->cur_frame_idx; i < td->nb_units; i++) {
|
||||
CodedBitstreamUnit *unit = &td->units[i];
|
||||
|
||||
size += unit->data_size;
|
||||
if (unit->type == AV1_OBU_FRAME) {
|
||||
AV1RawOBU *obu = unit->content;
|
||||
|
||||
if (frame) {
|
||||
av_log(ctx, AV_LOG_WARNING, "Frame OBU found when Tile data for a "
|
||||
"previous frame was expected.\n");
|
||||
goto passthrough;
|
||||
}
|
||||
|
||||
frame = &obu->obu.frame.header;
|
||||
cur_frame_type = obu->header.obu_type;
|
||||
s->last_frame_idx = s->cur_frame_idx;
|
||||
s->cur_frame_idx = i + 1;
|
||||
s->cur_frame++;
|
||||
|
||||
// split here unless it's the last frame, in which case
|
||||
// include every trailing OBU
|
||||
if (s->cur_frame < s->nb_frames)
|
||||
break;
|
||||
} else if (unit->type == AV1_OBU_FRAME_HEADER) {
|
||||
AV1RawOBU *obu = unit->content;
|
||||
|
||||
if (frame) {
|
||||
av_log(ctx, AV_LOG_WARNING, "Frame Header OBU found when Tile data for a "
|
||||
"previous frame was expected.\n");
|
||||
goto passthrough;
|
||||
}
|
||||
|
||||
frame = &obu->obu.frame_header;
|
||||
cur_frame_type = obu->header.obu_type;
|
||||
s->last_frame_idx = s->cur_frame_idx;
|
||||
s->cur_frame++;
|
||||
|
||||
// split here if show_existing_frame unless it's the last
|
||||
// frame, in which case include every trailing OBU
|
||||
if (frame->show_existing_frame &&
|
||||
s->cur_frame < s->nb_frames) {
|
||||
s->cur_frame_idx = i + 1;
|
||||
break;
|
||||
}
|
||||
} else if (unit->type == AV1_OBU_TILE_GROUP) {
|
||||
AV1RawOBU *obu = unit->content;
|
||||
AV1RawTileGroup *group = &obu->obu.tile_group;
|
||||
|
||||
if (!frame || cur_frame_type != AV1_OBU_FRAME_HEADER) {
|
||||
av_log(ctx, AV_LOG_WARNING, "Unexpected Tile Group OBU found before a "
|
||||
"Frame Header.\n");
|
||||
goto passthrough;
|
||||
}
|
||||
|
||||
if ((group->tg_end == (frame->tile_cols * frame->tile_rows) - 1) &&
|
||||
// include every trailing OBU with the last frame
|
||||
s->cur_frame < s->nb_frames) {
|
||||
s->cur_frame_idx = i + 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
av_assert0(frame && s->cur_frame <= s->nb_frames);
|
||||
|
||||
ret = av_packet_ref(out, s->buffer_pkt);
|
||||
if (ret < 0)
|
||||
goto fail;
|
||||
|
||||
out->data = (uint8_t *)td->units[s->last_frame_idx].data;
|
||||
out->size = size;
|
||||
|
||||
// skip the frame in the buffer packet if it's split successfully, so it's not present
|
||||
// if the packet is passed through in case of failure when splitting another frame.
|
||||
s->buffer_pkt->data += size;
|
||||
s->buffer_pkt->size -= size;
|
||||
|
||||
if (!frame->show_existing_frame && !frame->show_frame)
|
||||
out->pts = AV_NOPTS_VALUE;
|
||||
|
||||
if (s->cur_frame == s->nb_frames) {
|
||||
av_packet_unref(s->buffer_pkt);
|
||||
ff_cbs_fragment_reset(td);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
passthrough:
|
||||
av_packet_move_ref(out, s->buffer_pkt);
|
||||
|
||||
ret = 0;
|
||||
fail:
|
||||
if (ret < 0) {
|
||||
av_packet_unref(out);
|
||||
av_packet_unref(s->buffer_pkt);
|
||||
}
|
||||
ff_cbs_fragment_reset(td);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static const CodedBitstreamUnitType decompose_unit_types[] = {
|
||||
AV1_OBU_TEMPORAL_DELIMITER,
|
||||
AV1_OBU_SEQUENCE_HEADER,
|
||||
AV1_OBU_FRAME_HEADER,
|
||||
AV1_OBU_TILE_GROUP,
|
||||
AV1_OBU_FRAME,
|
||||
};
|
||||
|
||||
static int av1_frame_split_init(AVBSFContext *ctx)
|
||||
{
|
||||
AV1FSplitContext *s = ctx->priv_data;
|
||||
CodedBitstreamFragment *td = &s->temporal_unit;
|
||||
int ret;
|
||||
|
||||
s->buffer_pkt = av_packet_alloc();
|
||||
if (!s->buffer_pkt)
|
||||
return AVERROR(ENOMEM);
|
||||
|
||||
ret = ff_cbs_init(&s->cbc, AV_CODEC_ID_AV1, ctx);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
s->cbc->decompose_unit_types = decompose_unit_types;
|
||||
s->cbc->nb_decompose_unit_types = FF_ARRAY_ELEMS(decompose_unit_types);
|
||||
|
||||
if (!ctx->par_in->extradata_size)
|
||||
return 0;
|
||||
|
||||
ret = ff_cbs_read_extradata(s->cbc, td, ctx->par_in);
|
||||
if (ret < 0)
|
||||
av_log(ctx, AV_LOG_WARNING, "Failed to parse extradata.\n");
|
||||
|
||||
ff_cbs_fragment_reset(td);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void av1_frame_split_flush(AVBSFContext *ctx)
|
||||
{
|
||||
AV1FSplitContext *s = ctx->priv_data;
|
||||
|
||||
av_packet_unref(s->buffer_pkt);
|
||||
ff_cbs_fragment_reset(&s->temporal_unit);
|
||||
}
|
||||
|
||||
static void av1_frame_split_close(AVBSFContext *ctx)
|
||||
{
|
||||
AV1FSplitContext *s = ctx->priv_data;
|
||||
|
||||
av_packet_free(&s->buffer_pkt);
|
||||
ff_cbs_fragment_free(&s->temporal_unit);
|
||||
ff_cbs_close(&s->cbc);
|
||||
}
|
||||
|
||||
static const enum AVCodecID av1_frame_split_codec_ids[] = {
|
||||
AV_CODEC_ID_AV1, AV_CODEC_ID_NONE,
|
||||
};
|
||||
|
||||
const FFBitStreamFilter ff_av1_frame_split_bsf = {
|
||||
.p.name = "av1_frame_split",
|
||||
.p.codec_ids = av1_frame_split_codec_ids,
|
||||
.priv_data_size = sizeof(AV1FSplitContext),
|
||||
.init = av1_frame_split_init,
|
||||
.flush = av1_frame_split_flush,
|
||||
.close = av1_frame_split_close,
|
||||
.filter = av1_frame_split_filter,
|
||||
};
|
||||
@@ -0,0 +1,231 @@
|
||||
/*
|
||||
* 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/common.h"
|
||||
#include "libavutil/opt.h"
|
||||
|
||||
#include "bsf.h"
|
||||
#include "bsf_internal.h"
|
||||
#include "cbs.h"
|
||||
#include "cbs_bsf.h"
|
||||
#include "cbs_av1.h"
|
||||
|
||||
typedef struct AV1MetadataContext {
|
||||
CBSBSFContext common;
|
||||
|
||||
int td;
|
||||
AV1RawOBU td_obu;
|
||||
|
||||
int color_primaries;
|
||||
int transfer_characteristics;
|
||||
int matrix_coefficients;
|
||||
|
||||
int color_range;
|
||||
int chroma_sample_position;
|
||||
|
||||
AVRational tick_rate;
|
||||
int num_ticks_per_picture;
|
||||
|
||||
int delete_padding;
|
||||
} AV1MetadataContext;
|
||||
|
||||
|
||||
static int av1_metadata_update_sequence_header(AVBSFContext *bsf,
|
||||
AV1RawSequenceHeader *seq)
|
||||
{
|
||||
AV1MetadataContext *ctx = bsf->priv_data;
|
||||
AV1RawColorConfig *clc = &seq->color_config;
|
||||
AV1RawTimingInfo *tim = &seq->timing_info;
|
||||
|
||||
if (ctx->color_primaries >= 0 ||
|
||||
ctx->transfer_characteristics >= 0 ||
|
||||
ctx->matrix_coefficients >= 0) {
|
||||
clc->color_description_present_flag = 1;
|
||||
|
||||
if (ctx->color_primaries >= 0)
|
||||
clc->color_primaries = ctx->color_primaries;
|
||||
if (ctx->transfer_characteristics >= 0)
|
||||
clc->transfer_characteristics = ctx->transfer_characteristics;
|
||||
if (ctx->matrix_coefficients >= 0)
|
||||
clc->matrix_coefficients = ctx->matrix_coefficients;
|
||||
}
|
||||
|
||||
if (ctx->color_range >= 0) {
|
||||
if (clc->color_primaries == AVCOL_PRI_BT709 &&
|
||||
clc->transfer_characteristics == AVCOL_TRC_IEC61966_2_1 &&
|
||||
clc->matrix_coefficients == AVCOL_SPC_RGB) {
|
||||
av_log(bsf, AV_LOG_WARNING, "Warning: color_range cannot be set "
|
||||
"on RGB streams encoded in BT.709 sRGB.\n");
|
||||
} else {
|
||||
clc->color_range = ctx->color_range;
|
||||
}
|
||||
}
|
||||
|
||||
if (ctx->chroma_sample_position >= 0) {
|
||||
if (clc->mono_chrome || !clc->subsampling_x || !clc->subsampling_y) {
|
||||
av_log(bsf, AV_LOG_WARNING, "Warning: chroma_sample_position "
|
||||
"can only be set for 4:2:0 streams.\n");
|
||||
} else {
|
||||
clc->chroma_sample_position = ctx->chroma_sample_position;
|
||||
}
|
||||
}
|
||||
|
||||
if (ctx->tick_rate.num && ctx->tick_rate.den) {
|
||||
int num, den;
|
||||
|
||||
av_reduce(&num, &den, ctx->tick_rate.num, ctx->tick_rate.den,
|
||||
UINT32_MAX > INT_MAX ? UINT32_MAX : INT_MAX);
|
||||
|
||||
tim->time_scale = num;
|
||||
tim->num_units_in_display_tick = den;
|
||||
seq->timing_info_present_flag = 1;
|
||||
|
||||
if (ctx->num_ticks_per_picture > 0) {
|
||||
tim->equal_picture_interval = 1;
|
||||
tim->num_ticks_per_picture_minus_1 =
|
||||
ctx->num_ticks_per_picture - 1;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int av1_metadata_update_fragment(AVBSFContext *bsf, AVPacket *pkt,
|
||||
CodedBitstreamFragment *frag)
|
||||
{
|
||||
AV1MetadataContext *ctx = bsf->priv_data;
|
||||
int err, i;
|
||||
|
||||
for (i = 0; i < frag->nb_units; i++) {
|
||||
if (frag->units[i].type == AV1_OBU_SEQUENCE_HEADER) {
|
||||
AV1RawOBU *obu = frag->units[i].content;
|
||||
err = av1_metadata_update_sequence_header(bsf, &obu->obu.sequence_header);
|
||||
if (err < 0)
|
||||
return err;
|
||||
}
|
||||
}
|
||||
|
||||
// If a Temporal Delimiter is present, it must be the first OBU.
|
||||
if (frag->nb_units && frag->units[0].type == AV1_OBU_TEMPORAL_DELIMITER) {
|
||||
if (ctx->td == BSF_ELEMENT_REMOVE)
|
||||
ff_cbs_delete_unit(frag, 0);
|
||||
} else if (pkt && ctx->td == BSF_ELEMENT_INSERT) {
|
||||
err = ff_cbs_insert_unit_content(frag, 0, AV1_OBU_TEMPORAL_DELIMITER,
|
||||
&ctx->td_obu, NULL);
|
||||
if (err < 0) {
|
||||
av_log(bsf, AV_LOG_ERROR, "Failed to insert Temporal Delimiter.\n");
|
||||
return err;
|
||||
}
|
||||
}
|
||||
|
||||
if (ctx->delete_padding) {
|
||||
for (i = frag->nb_units - 1; i >= 0; i--) {
|
||||
if (frag->units[i].type == AV1_OBU_PADDING)
|
||||
ff_cbs_delete_unit(frag, i);
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const CBSBSFType av1_metadata_type = {
|
||||
.codec_id = AV_CODEC_ID_AV1,
|
||||
.fragment_name = "temporal unit",
|
||||
.unit_name = "OBU",
|
||||
.update_fragment = &av1_metadata_update_fragment,
|
||||
};
|
||||
|
||||
static int av1_metadata_init(AVBSFContext *bsf)
|
||||
{
|
||||
AV1MetadataContext *ctx = bsf->priv_data;
|
||||
|
||||
ctx->td_obu = (AV1RawOBU) {
|
||||
.header.obu_type = AV1_OBU_TEMPORAL_DELIMITER,
|
||||
};
|
||||
|
||||
return ff_cbs_bsf_generic_init(bsf, &av1_metadata_type);
|
||||
}
|
||||
|
||||
#define OFFSET(x) offsetof(AV1MetadataContext, x)
|
||||
#define FLAGS (AV_OPT_FLAG_VIDEO_PARAM|AV_OPT_FLAG_BSF_PARAM)
|
||||
static const AVOption av1_metadata_options[] = {
|
||||
BSF_ELEMENT_OPTIONS_PIR("td", "Temporal Delimiter OBU",
|
||||
td, FLAGS),
|
||||
|
||||
{ "color_primaries", "Set color primaries (section 6.4.2)",
|
||||
OFFSET(color_primaries), AV_OPT_TYPE_INT,
|
||||
{ .i64 = -1 }, -1, 255, FLAGS },
|
||||
{ "transfer_characteristics", "Set transfer characteristics (section 6.4.2)",
|
||||
OFFSET(transfer_characteristics), AV_OPT_TYPE_INT,
|
||||
{ .i64 = -1 }, -1, 255, FLAGS },
|
||||
{ "matrix_coefficients", "Set matrix coefficients (section 6.4.2)",
|
||||
OFFSET(matrix_coefficients), AV_OPT_TYPE_INT,
|
||||
{ .i64 = -1 }, -1, 255, FLAGS },
|
||||
|
||||
{ "color_range", "Set color range flag (section 6.4.2)",
|
||||
OFFSET(color_range), AV_OPT_TYPE_INT,
|
||||
{ .i64 = -1 }, -1, 1, FLAGS, .unit = "cr" },
|
||||
{ "tv", "TV (limited) range", 0, AV_OPT_TYPE_CONST,
|
||||
{ .i64 = 0 }, .flags = FLAGS, .unit = "cr" },
|
||||
{ "pc", "PC (full) range", 0, AV_OPT_TYPE_CONST,
|
||||
{ .i64 = 1 }, .flags = FLAGS, .unit = "cr" },
|
||||
|
||||
{ "chroma_sample_position", "Set chroma sample position (section 6.4.2)",
|
||||
OFFSET(chroma_sample_position), AV_OPT_TYPE_INT,
|
||||
{ .i64 = -1 }, -1, 3, FLAGS, .unit = "csp" },
|
||||
{ "unknown", "Unknown chroma sample position", 0, AV_OPT_TYPE_CONST,
|
||||
{ .i64 = AV1_CSP_UNKNOWN }, .flags = FLAGS, .unit = "csp" },
|
||||
{ "vertical", "Left chroma sample position", 0, AV_OPT_TYPE_CONST,
|
||||
{ .i64 = AV1_CSP_VERTICAL }, .flags = FLAGS, .unit = "csp" },
|
||||
{ "colocated", "Top-left chroma sample position", 0, AV_OPT_TYPE_CONST,
|
||||
{ .i64 = AV1_CSP_COLOCATED }, .flags = FLAGS, .unit = "csp" },
|
||||
|
||||
{ "tick_rate", "Set display tick rate (time_scale / num_units_in_display_tick)",
|
||||
OFFSET(tick_rate), AV_OPT_TYPE_RATIONAL,
|
||||
{ .dbl = 0.0 }, 0, UINT_MAX, FLAGS },
|
||||
{ "num_ticks_per_picture", "Set display ticks per picture for CFR streams",
|
||||
OFFSET(num_ticks_per_picture), AV_OPT_TYPE_INT,
|
||||
{ .i64 = -1 }, -1, INT_MAX, FLAGS },
|
||||
|
||||
{ "delete_padding", "Delete all Padding OBUs",
|
||||
OFFSET(delete_padding), AV_OPT_TYPE_BOOL,
|
||||
{ .i64 = 0 }, 0, 1, FLAGS},
|
||||
|
||||
{ NULL }
|
||||
};
|
||||
|
||||
static const AVClass av1_metadata_class = {
|
||||
.class_name = "av1_metadata_bsf",
|
||||
.item_name = av_default_item_name,
|
||||
.option = av1_metadata_options,
|
||||
.version = LIBAVUTIL_VERSION_INT,
|
||||
};
|
||||
|
||||
static const enum AVCodecID av1_metadata_codec_ids[] = {
|
||||
AV_CODEC_ID_AV1, AV_CODEC_ID_NONE,
|
||||
};
|
||||
|
||||
const FFBitStreamFilter ff_av1_metadata_bsf = {
|
||||
.p.name = "av1_metadata",
|
||||
.p.codec_ids = av1_metadata_codec_ids,
|
||||
.p.priv_class = &av1_metadata_class,
|
||||
.priv_data_size = sizeof(AV1MetadataContext),
|
||||
.init = &av1_metadata_init,
|
||||
.close = &ff_cbs_bsf_generic_close,
|
||||
.filter = &ff_cbs_bsf_generic_filter,
|
||||
};
|
||||
@@ -0,0 +1,45 @@
|
||||
/*
|
||||
* Chomp bitstream filter
|
||||
* Copyright (c) 2010 Alex Converse <alex.converse@gmail.com>
|
||||
*
|
||||
* 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 "bsf.h"
|
||||
#include "bsf_internal.h"
|
||||
|
||||
static int chomp_filter(AVBSFContext *ctx, AVPacket *pkt)
|
||||
{
|
||||
int ret;
|
||||
|
||||
ret = ff_bsf_get_packet_ref(ctx, pkt);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
while (pkt->size > 0 && !pkt->data[pkt->size - 1])
|
||||
pkt->size--;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* This filter removes a string of NULL bytes from the end of a packet.
|
||||
*/
|
||||
const FFBitStreamFilter ff_chomp_bsf = {
|
||||
.p.name = "chomp",
|
||||
.filter = chomp_filter,
|
||||
};
|
||||
@@ -0,0 +1,61 @@
|
||||
/*
|
||||
* Copyright (c) 2016 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 "bsf.h"
|
||||
#include "bsf_internal.h"
|
||||
#include "bytestream.h"
|
||||
#include "dca_syncwords.h"
|
||||
|
||||
static int dca_core_filter(AVBSFContext *ctx, AVPacket *pkt)
|
||||
{
|
||||
GetByteContext gb;
|
||||
uint32_t syncword;
|
||||
int core_size = 0, ret;
|
||||
|
||||
ret = ff_bsf_get_packet_ref(ctx, pkt);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
bytestream2_init(&gb, pkt->data, pkt->size);
|
||||
syncword = bytestream2_get_be32(&gb);
|
||||
bytestream2_skip(&gb, 1);
|
||||
|
||||
switch (syncword) {
|
||||
case DCA_SYNCWORD_CORE_BE:
|
||||
core_size = ((bytestream2_get_be24(&gb) >> 4) & 0x3fff) + 1;
|
||||
break;
|
||||
}
|
||||
|
||||
if (core_size > 0 && core_size <= pkt->size) {
|
||||
pkt->size = core_size;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const enum AVCodecID codec_ids[] = {
|
||||
AV_CODEC_ID_DTS, AV_CODEC_ID_NONE,
|
||||
};
|
||||
|
||||
const FFBitStreamFilter ff_dca_core_bsf = {
|
||||
.p.name = "dca_core",
|
||||
.p.codec_ids = codec_ids,
|
||||
.filter = dca_core_filter,
|
||||
};
|
||||
@@ -0,0 +1,289 @@
|
||||
/*
|
||||
* 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/common.h"
|
||||
#include "libavutil/mem.h"
|
||||
#include "libavutil/opt.h"
|
||||
|
||||
#include "bsf.h"
|
||||
#include "bsf_internal.h"
|
||||
#include "cbs.h"
|
||||
#include "cbs_bsf.h"
|
||||
#include "cbs_av1.h"
|
||||
#include "cbs_h265.h"
|
||||
#include "dovi_rpu.h"
|
||||
#include "h2645data.h"
|
||||
#include "h265_profile_level.h"
|
||||
#include "itut35.h"
|
||||
|
||||
#include "hevc/hevc.h"
|
||||
|
||||
typedef struct DoviRpuContext {
|
||||
CBSBSFContext common;
|
||||
DOVIContext dec;
|
||||
DOVIContext enc;
|
||||
|
||||
int strip;
|
||||
int compression;
|
||||
} DoviRpuContext;
|
||||
|
||||
static int update_rpu(AVBSFContext *bsf, const AVPacket *pkt, int flags,
|
||||
const uint8_t *rpu, size_t rpu_size,
|
||||
uint8_t **out_rpu, int *out_size)
|
||||
{
|
||||
DoviRpuContext *s = bsf->priv_data;
|
||||
AVDOVIMetadata *metadata = NULL;
|
||||
int ret;
|
||||
|
||||
ret = ff_dovi_rpu_parse(&s->dec, rpu, rpu_size, 0);
|
||||
if (ret < 0) {
|
||||
ff_dovi_ctx_flush(&s->dec);
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret = ff_dovi_get_metadata(&s->dec, &metadata);
|
||||
if (ret == 0 /* no metadata */) {
|
||||
*out_rpu = NULL;
|
||||
*out_size = 0;
|
||||
return 0;
|
||||
} else if (ret < 0) {
|
||||
ff_dovi_ctx_flush(&s->dec);
|
||||
return ret;
|
||||
}
|
||||
|
||||
if (pkt && !(pkt->flags & AV_PKT_FLAG_KEY))
|
||||
flags |= FF_DOVI_COMPRESS_RPU;
|
||||
ret = ff_dovi_rpu_generate(&s->enc, metadata, flags, out_rpu, out_size);
|
||||
av_free(metadata);
|
||||
if (ret < 0)
|
||||
ff_dovi_ctx_flush(&s->enc);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int dovi_rpu_update_fragment_hevc(AVBSFContext *bsf, AVPacket *pkt,
|
||||
CodedBitstreamFragment *au)
|
||||
{
|
||||
DoviRpuContext *s = bsf->priv_data;
|
||||
CodedBitstreamUnit *nal = au->nb_units ? &au->units[au->nb_units - 1] : NULL;
|
||||
uint8_t *rpu = NULL;
|
||||
int rpu_size, ret;
|
||||
|
||||
// HEVC_NAL_UNSPEC62 is Dolby Vision PRU and HEVC_NAL_UNSPEC63 is Dolby Vision EL
|
||||
if (!nal || (nal->type != HEVC_NAL_UNSPEC62 && nal->type != HEVC_NAL_UNSPEC63))
|
||||
return 0;
|
||||
|
||||
if (s->strip) {
|
||||
ff_cbs_delete_unit(au, au->nb_units - 1);
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (nal->type == HEVC_NAL_UNSPEC63)
|
||||
return 0;
|
||||
|
||||
ret = update_rpu(bsf, pkt, 0, nal->data + 2, nal->data_size - 2, &rpu, &rpu_size);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
/* NAL unit header + NAL prefix */
|
||||
if (rpu_size + 3 <= nal->data_size && av_buffer_is_writable(nal->data_ref)) {
|
||||
memcpy(nal->data + 3, rpu, rpu_size);
|
||||
av_free(rpu);
|
||||
nal->data_size = rpu_size + 3;
|
||||
} else {
|
||||
AVBufferRef *ref = av_buffer_alloc(rpu_size + 3);
|
||||
if (!ref) {
|
||||
av_free(rpu);
|
||||
return AVERROR(ENOMEM);
|
||||
}
|
||||
|
||||
memcpy(ref->data, nal->data, 3);
|
||||
memcpy(ref->data + 3, rpu, rpu_size);
|
||||
av_buffer_unref(&nal->data_ref);
|
||||
av_free(rpu);
|
||||
nal->data = ref->data;
|
||||
nal->data_size = rpu_size + 3;
|
||||
nal->data_ref = ref;
|
||||
nal->data_bit_padding = 0;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int dovi_rpu_update_fragment_av1(AVBSFContext *bsf, AVPacket *pkt,
|
||||
CodedBitstreamFragment *frag)
|
||||
{
|
||||
DoviRpuContext *s = bsf->priv_data;
|
||||
int provider_code, provider_oriented_code, rpu_size, ret;
|
||||
AVBufferRef *ref;
|
||||
uint8_t *rpu;
|
||||
|
||||
for (int i = 0; i < frag->nb_units; i++) {
|
||||
AV1RawOBU *obu = frag->units[i].content;
|
||||
AV1RawMetadataITUTT35 *t35 = &obu->obu.metadata.metadata.itut_t35;
|
||||
if (frag->units[i].type != AV1_OBU_METADATA ||
|
||||
obu->obu.metadata.metadata_type != AV1_METADATA_TYPE_ITUT_T35 ||
|
||||
t35->itu_t_t35_country_code != ITU_T_T35_COUNTRY_CODE_US ||
|
||||
t35->payload_size < 6)
|
||||
continue;
|
||||
|
||||
provider_code = AV_RB16(t35->payload);
|
||||
provider_oriented_code = AV_RB32(t35->payload + 2);
|
||||
if (provider_code != ITU_T_T35_PROVIDER_CODE_DOLBY ||
|
||||
provider_oriented_code != 0x800)
|
||||
continue;
|
||||
|
||||
if (s->strip) {
|
||||
ff_cbs_delete_unit(frag, i);
|
||||
return 0;
|
||||
}
|
||||
|
||||
ret = update_rpu(bsf, pkt, FF_DOVI_WRAP_T35,
|
||||
t35->payload + 6, t35->payload_size - 6,
|
||||
&rpu, &rpu_size);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
ref = av_buffer_create(rpu, rpu_size, av_buffer_default_free, NULL, 0);
|
||||
if (!ref) {
|
||||
av_free(rpu);
|
||||
return AVERROR(ENOMEM);
|
||||
}
|
||||
|
||||
av_buffer_unref(&t35->payload_ref);
|
||||
t35->payload_ref = ref;
|
||||
t35->payload = rpu + 1; /* skip country code */
|
||||
t35->payload_size = rpu_size - 1;
|
||||
break; /* should be only one RPU per packet */
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const CBSBSFType dovi_rpu_hevc_type = {
|
||||
.codec_id = AV_CODEC_ID_HEVC,
|
||||
.fragment_name = "access unit",
|
||||
.unit_name = "NAL unit",
|
||||
.update_fragment = &dovi_rpu_update_fragment_hevc,
|
||||
};
|
||||
|
||||
static const CBSBSFType dovi_rpu_av1_type = {
|
||||
.codec_id = AV_CODEC_ID_AV1,
|
||||
.fragment_name = "temporal unit",
|
||||
.unit_name = "OBU",
|
||||
.update_fragment = &dovi_rpu_update_fragment_av1,
|
||||
};
|
||||
|
||||
static int dovi_rpu_init(AVBSFContext *bsf)
|
||||
{
|
||||
int ret;
|
||||
DoviRpuContext *s = bsf->priv_data;
|
||||
s->dec.logctx = s->enc.logctx = bsf;
|
||||
s->enc.enable = 1;
|
||||
|
||||
if (s->compression == AV_DOVI_COMPRESSION_RESERVED) {
|
||||
av_log(bsf, AV_LOG_ERROR, "Invalid compression level: %d\n", s->compression);
|
||||
return AVERROR(EINVAL);
|
||||
}
|
||||
|
||||
if (s->strip) {
|
||||
av_packet_side_data_remove(bsf->par_out->coded_side_data,
|
||||
&bsf->par_out->nb_coded_side_data,
|
||||
AV_PKT_DATA_DOVI_CONF);
|
||||
} else {
|
||||
const AVPacketSideData *sd;
|
||||
sd = av_packet_side_data_get(bsf->par_out->coded_side_data,
|
||||
bsf->par_out->nb_coded_side_data,
|
||||
AV_PKT_DATA_DOVI_CONF);
|
||||
|
||||
if (sd) {
|
||||
AVDOVIDecoderConfigurationRecord *cfg;
|
||||
cfg = (AVDOVIDecoderConfigurationRecord *) sd->data;
|
||||
s->dec.cfg = *cfg;
|
||||
|
||||
/* Update configuration record before setting to enc ctx */
|
||||
cfg->dv_md_compression = s->compression;
|
||||
if (s->compression && s->dec.cfg.dv_profile < 8) {
|
||||
av_log(bsf, AV_LOG_ERROR, "Invalid compression level %d for "
|
||||
"Dolby Vision profile %d.\n", s->compression, s->dec.cfg.dv_profile);
|
||||
return AVERROR(EINVAL);
|
||||
}
|
||||
|
||||
s->enc.cfg = *cfg;
|
||||
} else {
|
||||
av_log(bsf, AV_LOG_WARNING, "No Dolby Vision configuration record "
|
||||
"found? Generating one, but results may be invalid.\n");
|
||||
ret = ff_dovi_configure_from_codedpar(&s->enc, bsf->par_out, NULL, s->compression,
|
||||
FF_COMPLIANCE_NORMAL);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
/* Be conservative in accepting all compressed RPUs */
|
||||
s->dec.cfg = s->enc.cfg;
|
||||
s->dec.cfg.dv_md_compression = AV_DOVI_COMPRESSION_EXTENDED;
|
||||
}
|
||||
}
|
||||
|
||||
switch (bsf->par_in->codec_id) {
|
||||
case AV_CODEC_ID_HEVC:
|
||||
return ff_cbs_bsf_generic_init(bsf, &dovi_rpu_hevc_type);
|
||||
case AV_CODEC_ID_AV1:
|
||||
return ff_cbs_bsf_generic_init(bsf, &dovi_rpu_av1_type);
|
||||
default:
|
||||
return AVERROR_BUG;
|
||||
}
|
||||
}
|
||||
|
||||
static void dovi_rpu_close(AVBSFContext *bsf)
|
||||
{
|
||||
DoviRpuContext *s = bsf->priv_data;
|
||||
ff_dovi_ctx_unref(&s->dec);
|
||||
ff_dovi_ctx_unref(&s->enc);
|
||||
ff_cbs_bsf_generic_close(bsf);
|
||||
}
|
||||
|
||||
#define OFFSET(x) offsetof(DoviRpuContext, x)
|
||||
#define FLAGS (AV_OPT_FLAG_VIDEO_PARAM|AV_OPT_FLAG_BSF_PARAM)
|
||||
static const AVOption dovi_rpu_options[] = {
|
||||
{ "strip", "Strip Dolby Vision metadata", OFFSET(strip), AV_OPT_TYPE_BOOL, { .i64 = 0 }, 0, 1, FLAGS },
|
||||
{ "compression", "DV metadata compression mode", OFFSET(compression), AV_OPT_TYPE_INT, { .i64 = AV_DOVI_COMPRESSION_LIMITED }, 0, AV_DOVI_COMPRESSION_EXTENDED, FLAGS, .unit = "compression" },
|
||||
{ "none", "Don't compress metadata", 0, AV_OPT_TYPE_CONST, {.i64 = 0}, .flags = FLAGS, .unit = "compression" },
|
||||
{ "limited", "Limited metadata compression", 0, AV_OPT_TYPE_CONST, {.i64 = AV_DOVI_COMPRESSION_LIMITED}, .flags = FLAGS, .unit = "compression" },
|
||||
{ "extended", "Extended metadata compression",0, AV_OPT_TYPE_CONST, {.i64 = AV_DOVI_COMPRESSION_EXTENDED}, .flags = FLAGS, .unit = "compression" },
|
||||
{ NULL }
|
||||
};
|
||||
|
||||
static const AVClass dovi_rpu_class = {
|
||||
.class_name = "dovi_rpu_bsf",
|
||||
.item_name = av_default_item_name,
|
||||
.option = dovi_rpu_options,
|
||||
.version = LIBAVUTIL_VERSION_INT,
|
||||
};
|
||||
|
||||
static const enum AVCodecID dovi_rpu_codec_ids[] = {
|
||||
AV_CODEC_ID_HEVC, AV_CODEC_ID_AV1, AV_CODEC_ID_NONE,
|
||||
};
|
||||
|
||||
const FFBitStreamFilter ff_dovi_rpu_bsf = {
|
||||
.p.name = "dovi_rpu",
|
||||
.p.codec_ids = dovi_rpu_codec_ids,
|
||||
.p.priv_class = &dovi_rpu_class,
|
||||
.priv_data_size = sizeof(DoviRpuContext),
|
||||
.init = &dovi_rpu_init,
|
||||
.close = &dovi_rpu_close,
|
||||
.filter = &ff_cbs_bsf_generic_filter,
|
||||
};
|
||||
@@ -0,0 +1,550 @@
|
||||
/*
|
||||
* Copyright (c) 2022 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
|
||||
*/
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Derive PTS by reordering DTS from supported streams
|
||||
*/
|
||||
|
||||
#include "libavutil/avassert.h"
|
||||
#include "libavutil/fifo.h"
|
||||
#include "libavutil/mem.h"
|
||||
#include "libavutil/tree.h"
|
||||
|
||||
#include "bsf.h"
|
||||
#include "bsf_internal.h"
|
||||
#include "cbs.h"
|
||||
#include "cbs_h264.h"
|
||||
#include "h264_parse.h"
|
||||
#include "h264_ps.h"
|
||||
#include "libavutil/refstruct.h"
|
||||
|
||||
typedef struct DTS2PTSNode {
|
||||
int64_t dts;
|
||||
int64_t duration;
|
||||
int poc;
|
||||
int gop;
|
||||
} DTS2PTSNode;
|
||||
|
||||
typedef struct DTS2PTSFrame {
|
||||
AVPacket *pkt;
|
||||
int poc;
|
||||
int poc_diff;
|
||||
int gop;
|
||||
} DTS2PTSFrame;
|
||||
|
||||
typedef struct DTS2PTSH264Context {
|
||||
H264POCContext poc;
|
||||
SPS sps;
|
||||
int poc_diff;
|
||||
int last_poc;
|
||||
int highest_poc;
|
||||
int picture_structure;
|
||||
} DTS2PTSH264Context;
|
||||
|
||||
typedef struct DTS2PTSContext {
|
||||
struct AVTreeNode *root;
|
||||
AVFifo *fifo;
|
||||
AVRefStructPool *node_pool;
|
||||
|
||||
// Codec specific function pointers and constants
|
||||
int (*init)(AVBSFContext *ctx);
|
||||
int (*filter)(AVBSFContext *ctx);
|
||||
void (*flush)(AVBSFContext *ctx);
|
||||
size_t fifo_size;
|
||||
|
||||
CodedBitstreamContext *cbc;
|
||||
CodedBitstreamFragment au;
|
||||
|
||||
union {
|
||||
DTS2PTSH264Context h264;
|
||||
} u;
|
||||
|
||||
int nb_frame;
|
||||
int gop;
|
||||
int eof;
|
||||
} DTS2PTSContext;
|
||||
|
||||
// AVTreeNode callbacks
|
||||
static int cmp_insert(const void *key, const void *node)
|
||||
{
|
||||
int ret = ((const DTS2PTSNode *)key)->poc - ((const DTS2PTSNode *)node)->poc;
|
||||
if (!ret)
|
||||
ret = ((const DTS2PTSNode *)key)->gop - ((const DTS2PTSNode *)node)->gop;
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int cmp_find(const void *key, const void *node)
|
||||
{
|
||||
const DTS2PTSFrame * key1 = key;
|
||||
const DTS2PTSNode *node1 = node;
|
||||
int ret = FFDIFFSIGN(key1->poc, node1->poc);
|
||||
if (!ret)
|
||||
ret = key1->gop - node1->gop;
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int dec_poc(void *opaque, void *elem)
|
||||
{
|
||||
DTS2PTSNode *node = elem;
|
||||
int dec = *(int *)opaque;
|
||||
node->poc -= dec;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int free_node(void *opaque, void *elem)
|
||||
{
|
||||
DTS2PTSNode *node = elem;
|
||||
av_refstruct_unref(&node);
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Shared functions
|
||||
static int alloc_and_insert_node(AVBSFContext *ctx, int64_t ts, int64_t duration,
|
||||
int poc, int poc_diff, int gop)
|
||||
{
|
||||
DTS2PTSContext *s = ctx->priv_data;
|
||||
for (int i = 0; i < poc_diff; i++) {
|
||||
struct AVTreeNode *node = av_tree_node_alloc();
|
||||
DTS2PTSNode *poc_node, *ret;
|
||||
if (!node)
|
||||
return AVERROR(ENOMEM);
|
||||
poc_node = av_refstruct_pool_get(s->node_pool);
|
||||
if (!poc_node) {
|
||||
av_free(node);
|
||||
return AVERROR(ENOMEM);
|
||||
}
|
||||
if (i && ts != AV_NOPTS_VALUE)
|
||||
ts += duration / poc_diff;
|
||||
*poc_node = (DTS2PTSNode) { ts, duration, poc++, gop };
|
||||
ret = av_tree_insert(&s->root, poc_node, cmp_insert, &node);
|
||||
if (ret && ret != poc_node) {
|
||||
*ret = *poc_node;
|
||||
av_refstruct_unref(&poc_node);
|
||||
av_free(node);
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
// H.264
|
||||
static const CodedBitstreamUnitType h264_decompose_unit_types[] = {
|
||||
H264_NAL_SPS,
|
||||
H264_NAL_PPS,
|
||||
H264_NAL_IDR_SLICE,
|
||||
H264_NAL_SLICE,
|
||||
};
|
||||
|
||||
static int h264_init(AVBSFContext *ctx)
|
||||
{
|
||||
DTS2PTSContext *s = ctx->priv_data;
|
||||
DTS2PTSH264Context *h264 = &s->u.h264;
|
||||
|
||||
s->cbc->decompose_unit_types = h264_decompose_unit_types;
|
||||
s->cbc->nb_decompose_unit_types = FF_ARRAY_ELEMS(h264_decompose_unit_types);
|
||||
|
||||
s->nb_frame = -(ctx->par_in->video_delay << 1);
|
||||
h264->last_poc = h264->highest_poc = INT_MIN;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int get_mmco_reset(const H264RawSliceHeader *header)
|
||||
{
|
||||
if (header->nal_unit_header.nal_ref_idc == 0 ||
|
||||
!header->adaptive_ref_pic_marking_mode_flag)
|
||||
return 0;
|
||||
|
||||
for (int i = 0; i < H264_MAX_MMCO_COUNT; i++) {
|
||||
if (header->mmco[i].memory_management_control_operation == 0)
|
||||
return 0;
|
||||
else if (header->mmco[i].memory_management_control_operation == 5)
|
||||
return 1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int h264_queue_frame(AVBSFContext *ctx, AVPacket *pkt, int poc, int *queued)
|
||||
{
|
||||
DTS2PTSContext *s = ctx->priv_data;
|
||||
DTS2PTSH264Context *h264 = &s->u.h264;
|
||||
DTS2PTSFrame frame;
|
||||
int poc_diff, ret;
|
||||
|
||||
poc_diff = (h264->picture_structure == 3) + 1;
|
||||
if (h264->sps.frame_mbs_only_flag && h264->poc_diff)
|
||||
poc_diff = FFMIN(poc_diff, h264->poc_diff);
|
||||
if (poc < 0) {
|
||||
av_tree_enumerate(s->root, &poc_diff, NULL, dec_poc);
|
||||
s->nb_frame -= poc_diff;
|
||||
}
|
||||
// Check if there was a POC reset (Like an IDR slice)
|
||||
if (s->nb_frame > h264->highest_poc) {
|
||||
s->nb_frame = 0;
|
||||
s->gop = (s->gop + 1) % s->fifo_size;
|
||||
h264->highest_poc = h264->last_poc;
|
||||
}
|
||||
|
||||
ret = alloc_and_insert_node(ctx, pkt->dts, pkt->duration, s->nb_frame, poc_diff, s->gop);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
av_log(ctx, AV_LOG_DEBUG, "Queueing frame with POC %d, GOP %d, dts %"PRId64"\n",
|
||||
poc, s->gop, pkt->dts);
|
||||
s->nb_frame += poc_diff;
|
||||
|
||||
// Add frame to output FIFO only once
|
||||
if (*queued)
|
||||
return 0;
|
||||
|
||||
frame = (DTS2PTSFrame) { pkt, poc, poc_diff, s->gop };
|
||||
ret = av_fifo_write(s->fifo, &frame, 1);
|
||||
av_assert2(ret >= 0);
|
||||
*queued = 1;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int h264_filter(AVBSFContext *ctx)
|
||||
{
|
||||
DTS2PTSContext *s = ctx->priv_data;
|
||||
DTS2PTSH264Context *h264 = &s->u.h264;
|
||||
CodedBitstreamFragment *au = &s->au;
|
||||
AVPacket *in;
|
||||
int output_picture_number = INT_MIN;
|
||||
int field_poc[2];
|
||||
int queued = 0, ret;
|
||||
|
||||
ret = ff_bsf_get_packet(ctx, &in);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
ret = ff_cbs_read_packet(s->cbc, au, in);
|
||||
if (ret < 0) {
|
||||
av_log(ctx, AV_LOG_WARNING, "Failed to parse access unit.\n");
|
||||
goto fail;
|
||||
}
|
||||
|
||||
for (int i = 0; i < au->nb_units; i++) {
|
||||
CodedBitstreamUnit *unit = &au->units[i];
|
||||
|
||||
switch (unit->type) {
|
||||
case H264_NAL_IDR_SLICE:
|
||||
h264->poc.prev_frame_num = 0;
|
||||
h264->poc.prev_frame_num_offset = 0;
|
||||
h264->poc.prev_poc_msb =
|
||||
h264->poc.prev_poc_lsb = 0;
|
||||
// fall-through
|
||||
case H264_NAL_SLICE: {
|
||||
const H264RawSlice *slice = unit->content;
|
||||
const H264RawSliceHeader *header = &slice->header;
|
||||
const CodedBitstreamH264Context *cbs_h264 = s->cbc->priv_data;
|
||||
const H264RawSPS *sps = cbs_h264->active_sps;
|
||||
int got_reset;
|
||||
|
||||
if (!sps) {
|
||||
av_log(ctx, AV_LOG_ERROR, "No active SPS for a slice\n");
|
||||
goto fail;
|
||||
}
|
||||
// Initialize the SPS struct with the fields ff_h264_init_poc() cares about
|
||||
h264->sps.frame_mbs_only_flag = sps->frame_mbs_only_flag;
|
||||
h264->sps.log2_max_frame_num = sps->log2_max_frame_num_minus4 + 4;
|
||||
h264->sps.poc_type = sps->pic_order_cnt_type;
|
||||
h264->sps.log2_max_poc_lsb = sps->log2_max_pic_order_cnt_lsb_minus4 + 4;
|
||||
h264->sps.offset_for_non_ref_pic = sps->offset_for_non_ref_pic;
|
||||
h264->sps.offset_for_top_to_bottom_field = sps->offset_for_top_to_bottom_field;
|
||||
h264->sps.poc_cycle_length = sps->num_ref_frames_in_pic_order_cnt_cycle;
|
||||
for (int j = 0; j < h264->sps.poc_cycle_length; j++)
|
||||
h264->sps.offset_for_ref_frame[j] = sps->offset_for_ref_frame[j];
|
||||
|
||||
h264->picture_structure = sps->frame_mbs_only_flag ? 3 :
|
||||
(header->field_pic_flag ?
|
||||
header->field_pic_flag + header->bottom_field_flag : 3);
|
||||
|
||||
h264->poc.frame_num = header->frame_num;
|
||||
h264->poc.poc_lsb = header->pic_order_cnt_lsb;
|
||||
h264->poc.delta_poc_bottom = header->delta_pic_order_cnt_bottom;
|
||||
h264->poc.delta_poc[0] = header->delta_pic_order_cnt[0];
|
||||
h264->poc.delta_poc[1] = header->delta_pic_order_cnt[1];
|
||||
|
||||
field_poc[0] = field_poc[1] = INT_MAX;
|
||||
ret = ff_h264_init_poc(field_poc, &output_picture_number, &h264->sps,
|
||||
&h264->poc, h264->picture_structure,
|
||||
header->nal_unit_header.nal_ref_idc);
|
||||
if (ret < 0) {
|
||||
av_log(ctx, AV_LOG_ERROR, "ff_h264_init_poc() failure\n");
|
||||
goto fail;
|
||||
}
|
||||
|
||||
got_reset = get_mmco_reset(header);
|
||||
h264->poc.prev_frame_num = got_reset ? 0 : h264->poc.frame_num;
|
||||
h264->poc.prev_frame_num_offset = got_reset ? 0 : h264->poc.frame_num_offset;
|
||||
if (header->nal_unit_header.nal_ref_idc != 0) {
|
||||
h264->poc.prev_poc_msb = got_reset ? 0 : h264->poc.poc_msb;
|
||||
if (got_reset)
|
||||
h264->poc.prev_poc_lsb = h264->picture_structure == 2 ? 0 : field_poc[0];
|
||||
else
|
||||
h264->poc.prev_poc_lsb = h264->poc.poc_lsb;
|
||||
}
|
||||
|
||||
if (output_picture_number != h264->last_poc) {
|
||||
if (h264->last_poc != INT_MIN) {
|
||||
int64_t diff = FFABS(h264->last_poc - (int64_t)output_picture_number);
|
||||
|
||||
if ((output_picture_number < 0) && !h264->last_poc)
|
||||
h264->poc_diff = 0;
|
||||
else if (FFABS((int64_t)output_picture_number) < h264->poc_diff) {
|
||||
diff = FFABS(output_picture_number);
|
||||
h264->poc_diff = 0;
|
||||
}
|
||||
if ((!h264->poc_diff || (h264->poc_diff > diff)) && diff <= INT_MAX) {
|
||||
h264->poc_diff = diff;
|
||||
if (h264->poc_diff == 1 && h264->sps.frame_mbs_only_flag) {
|
||||
av_tree_enumerate(s->root, &h264->poc_diff, NULL, dec_poc);
|
||||
s->nb_frame -= 2;
|
||||
}
|
||||
}
|
||||
}
|
||||
h264->last_poc = output_picture_number;
|
||||
h264->highest_poc = FFMAX(h264->highest_poc, output_picture_number);
|
||||
|
||||
ret = h264_queue_frame(ctx, in, output_picture_number, &queued);
|
||||
if (ret < 0)
|
||||
goto fail;
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (output_picture_number == INT_MIN) {
|
||||
av_log(ctx, AV_LOG_ERROR, "No slices in access unit\n");
|
||||
ret = AVERROR_INVALIDDATA;
|
||||
goto fail;
|
||||
}
|
||||
|
||||
ret = 0;
|
||||
fail:
|
||||
ff_cbs_fragment_reset(au);
|
||||
if (!queued)
|
||||
av_packet_free(&in);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void h264_flush(AVBSFContext *ctx)
|
||||
{
|
||||
DTS2PTSContext *s = ctx->priv_data;
|
||||
DTS2PTSH264Context *h264 = &s->u.h264;
|
||||
|
||||
memset(&h264->sps, 0, sizeof(h264->sps));
|
||||
memset(&h264->poc, 0, sizeof(h264->poc));
|
||||
s->nb_frame = -(ctx->par_in->video_delay << 1);
|
||||
h264->last_poc = h264->highest_poc = INT_MIN;
|
||||
}
|
||||
|
||||
// Core functions
|
||||
static const struct {
|
||||
enum AVCodecID id;
|
||||
int (*init)(AVBSFContext *ctx);
|
||||
int (*filter)(AVBSFContext *ctx);
|
||||
void (*flush)(AVBSFContext *ctx);
|
||||
size_t fifo_size;
|
||||
} func_tab[] = {
|
||||
{ AV_CODEC_ID_H264, h264_init, h264_filter, h264_flush, H264_MAX_DPB_FRAMES * 2 * 2 },
|
||||
};
|
||||
|
||||
static int dts2pts_init(AVBSFContext *ctx)
|
||||
{
|
||||
DTS2PTSContext *s = ctx->priv_data;
|
||||
CodedBitstreamFragment *au = &s->au;
|
||||
int i, ret;
|
||||
|
||||
for (i = 0; i < FF_ARRAY_ELEMS(func_tab); i++) {
|
||||
if (func_tab[i].id == ctx->par_in->codec_id) {
|
||||
s->init = func_tab[i].init;
|
||||
s->filter = func_tab[i].filter;
|
||||
s->flush = func_tab[i].flush;
|
||||
s->fifo_size = func_tab[i].fifo_size;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (i == FF_ARRAY_ELEMS(func_tab))
|
||||
return AVERROR_BUG;
|
||||
av_assert0(s->filter && s->fifo_size);
|
||||
|
||||
s->fifo = av_fifo_alloc2(s->fifo_size, sizeof(DTS2PTSFrame), 0);
|
||||
if (!s->fifo)
|
||||
return AVERROR(ENOMEM);
|
||||
|
||||
s->node_pool = av_refstruct_pool_alloc(sizeof(DTS2PTSNode),
|
||||
AV_REFSTRUCT_POOL_FLAG_NO_ZEROING);
|
||||
|
||||
if (!s->node_pool)
|
||||
return AVERROR(ENOMEM);
|
||||
|
||||
ret = ff_cbs_init(&s->cbc, ctx->par_in->codec_id, ctx);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
if (s->init) {
|
||||
ret = s->init(ctx);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
}
|
||||
|
||||
if (!ctx->par_in->extradata_size)
|
||||
return 0;
|
||||
|
||||
ret = ff_cbs_read_extradata(s->cbc, au, ctx->par_in);
|
||||
if (ret < 0)
|
||||
av_log(ctx, AV_LOG_WARNING, "Failed to parse extradata.\n");
|
||||
|
||||
ff_cbs_fragment_reset(au);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int dts2pts_filter(AVBSFContext *ctx, AVPacket *out)
|
||||
{
|
||||
DTS2PTSContext *s = ctx->priv_data;
|
||||
DTS2PTSNode *poc_node = NULL, *next[2] = { NULL, NULL };
|
||||
DTS2PTSFrame frame;
|
||||
int ret;
|
||||
|
||||
// Fill up the FIFO and POC tree
|
||||
while (!s->eof && av_fifo_can_write(s->fifo)) {
|
||||
ret = s->filter(ctx);
|
||||
if (ret < 0) {
|
||||
if (ret != AVERROR_EOF)
|
||||
return ret;
|
||||
s->eof = 1;
|
||||
}
|
||||
}
|
||||
|
||||
if (!av_fifo_can_read(s->fifo))
|
||||
return AVERROR_EOF;
|
||||
|
||||
// Fetch a packet from the FIFO
|
||||
ret = av_fifo_read(s->fifo, &frame, 1);
|
||||
av_assert2(ret >= 0);
|
||||
av_packet_move_ref(out, frame.pkt);
|
||||
av_packet_free(&frame.pkt);
|
||||
|
||||
// Search the timestamp for the requested POC and set PTS
|
||||
poc_node = av_tree_find(s->root, &frame, cmp_find, (void **)next);
|
||||
if (!poc_node) {
|
||||
poc_node = next[1];
|
||||
if (!poc_node || poc_node->poc != frame.poc)
|
||||
poc_node = next[0];
|
||||
}
|
||||
if (poc_node && poc_node->poc == frame.poc) {
|
||||
out->pts = poc_node->dts;
|
||||
if (!s->eof) {
|
||||
// Remove the found entry from the tree
|
||||
DTS2PTSFrame dup = (DTS2PTSFrame) { NULL, frame.poc + 1, frame.poc_diff, frame.gop };
|
||||
for (; dup.poc_diff > 0; dup.poc++, dup.poc_diff--) {
|
||||
struct AVTreeNode *node = NULL;
|
||||
if (!poc_node || poc_node->dts != out->pts)
|
||||
continue;
|
||||
av_tree_insert(&s->root, poc_node, cmp_insert, &node);
|
||||
av_refstruct_unref(&poc_node);
|
||||
av_free(node);
|
||||
poc_node = av_tree_find(s->root, &dup, cmp_find, NULL);
|
||||
}
|
||||
}
|
||||
} else if (s->eof && frame.poc > INT_MIN) {
|
||||
DTS2PTSFrame dup = (DTS2PTSFrame) { NULL, frame.poc - 1, frame.poc_diff, frame.gop };
|
||||
poc_node = av_tree_find(s->root, &dup, cmp_find, NULL);
|
||||
if (poc_node && poc_node->poc == dup.poc) {
|
||||
out->pts = poc_node->dts;
|
||||
if (out->pts != AV_NOPTS_VALUE)
|
||||
out->pts += poc_node->duration;
|
||||
ret = alloc_and_insert_node(ctx, out->pts, out->duration,
|
||||
frame.poc, frame.poc_diff, frame.gop);
|
||||
if (ret < 0) {
|
||||
av_packet_unref(out);
|
||||
return ret;
|
||||
}
|
||||
if (!ret)
|
||||
av_log(ctx, AV_LOG_DEBUG, "Queueing frame for POC %d, GOP %d, dts %"PRId64", "
|
||||
"generated from POC %d, GOP %d, dts %"PRId64", duration %"PRId64"\n",
|
||||
frame.poc, frame.gop, out->pts,
|
||||
poc_node->poc, poc_node->gop, poc_node->dts, poc_node->duration);
|
||||
} else
|
||||
av_log(ctx, AV_LOG_WARNING, "No timestamp for POC %d in tree\n", frame.poc);
|
||||
} else
|
||||
av_log(ctx, AV_LOG_WARNING, "No timestamp for POC %d in tree\n", frame.poc);
|
||||
av_log(ctx, AV_LOG_DEBUG, "Returning frame for POC %d, GOP %d, dts %"PRId64", pts %"PRId64"\n",
|
||||
frame.poc, frame.gop, out->dts, out->pts);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void dts2pts_flush(AVBSFContext *ctx)
|
||||
{
|
||||
DTS2PTSContext *s = ctx->priv_data;
|
||||
DTS2PTSFrame frame;
|
||||
|
||||
if (s->flush)
|
||||
s->flush(ctx);
|
||||
s->eof = 0;
|
||||
s->gop = 0;
|
||||
|
||||
while (s->fifo && av_fifo_read(s->fifo, &frame, 1) >= 0)
|
||||
av_packet_free(&frame.pkt);
|
||||
|
||||
av_tree_enumerate(s->root, NULL, NULL, free_node);
|
||||
av_tree_destroy(s->root);
|
||||
s->root = NULL;
|
||||
|
||||
ff_cbs_fragment_reset(&s->au);
|
||||
if (s->cbc)
|
||||
ff_cbs_flush(s->cbc);
|
||||
}
|
||||
|
||||
static void dts2pts_close(AVBSFContext *ctx)
|
||||
{
|
||||
DTS2PTSContext *s = ctx->priv_data;
|
||||
|
||||
dts2pts_flush(ctx);
|
||||
|
||||
av_fifo_freep2(&s->fifo);
|
||||
av_refstruct_pool_uninit(&s->node_pool);
|
||||
ff_cbs_fragment_free(&s->au);
|
||||
ff_cbs_close(&s->cbc);
|
||||
}
|
||||
|
||||
static const enum AVCodecID dts2pts_codec_ids[] = {
|
||||
AV_CODEC_ID_H264,
|
||||
AV_CODEC_ID_NONE,
|
||||
};
|
||||
|
||||
const FFBitStreamFilter ff_dts2pts_bsf = {
|
||||
.p.name = "dts2pts",
|
||||
.p.codec_ids = dts2pts_codec_ids,
|
||||
.priv_data_size = sizeof(DTS2PTSContext),
|
||||
.init = dts2pts_init,
|
||||
.flush = dts2pts_flush,
|
||||
.close = dts2pts_close,
|
||||
.filter = dts2pts_filter,
|
||||
};
|
||||
@@ -0,0 +1,106 @@
|
||||
/*
|
||||
* copyright (c) 2006 Michael Niedermayer <michaelni@gmx.at>
|
||||
*
|
||||
* 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 <string.h>
|
||||
|
||||
#include "bsf.h"
|
||||
#include "bsf_internal.h"
|
||||
|
||||
#include "libavutil/log.h"
|
||||
#include "libavutil/opt.h"
|
||||
|
||||
enum DumpFreq {
|
||||
DUMP_FREQ_KEYFRAME,
|
||||
DUMP_FREQ_ALL,
|
||||
};
|
||||
|
||||
typedef struct DumpExtradataContext {
|
||||
const AVClass *class;
|
||||
AVPacket pkt;
|
||||
int freq;
|
||||
} DumpExtradataContext;
|
||||
|
||||
static int dump_extradata(AVBSFContext *ctx, AVPacket *out)
|
||||
{
|
||||
DumpExtradataContext *s = ctx->priv_data;
|
||||
AVPacket *in = &s->pkt;
|
||||
int ret = 0;
|
||||
|
||||
ret = ff_bsf_get_packet_ref(ctx, in);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
if (ctx->par_in->extradata &&
|
||||
(s->freq == DUMP_FREQ_ALL ||
|
||||
(s->freq == DUMP_FREQ_KEYFRAME && in->flags & AV_PKT_FLAG_KEY)) &&
|
||||
(in->size < ctx->par_in->extradata_size ||
|
||||
memcmp(in->data, ctx->par_in->extradata, ctx->par_in->extradata_size))) {
|
||||
if (in->size >= INT_MAX - ctx->par_in->extradata_size) {
|
||||
ret = AVERROR(ERANGE);
|
||||
goto fail;
|
||||
}
|
||||
|
||||
ret = av_new_packet(out, in->size + ctx->par_in->extradata_size);
|
||||
if (ret < 0)
|
||||
goto fail;
|
||||
|
||||
ret = av_packet_copy_props(out, in);
|
||||
if (ret < 0) {
|
||||
av_packet_unref(out);
|
||||
goto fail;
|
||||
}
|
||||
|
||||
memcpy(out->data, ctx->par_in->extradata, ctx->par_in->extradata_size);
|
||||
memcpy(out->data + ctx->par_in->extradata_size, in->data, in->size);
|
||||
} else {
|
||||
av_packet_move_ref(out, in);
|
||||
}
|
||||
|
||||
fail:
|
||||
av_packet_unref(in);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
#define OFFSET(x) offsetof(DumpExtradataContext, x)
|
||||
#define FLAGS (AV_OPT_FLAG_VIDEO_PARAM|AV_OPT_FLAG_BSF_PARAM)
|
||||
static const AVOption options[] = {
|
||||
{ "freq", "When to dump extradata", OFFSET(freq), AV_OPT_TYPE_INT,
|
||||
{ .i64 = DUMP_FREQ_KEYFRAME }, DUMP_FREQ_KEYFRAME, DUMP_FREQ_ALL, FLAGS, .unit = "freq" },
|
||||
{ "k", NULL, 0, AV_OPT_TYPE_CONST, { .i64 = DUMP_FREQ_KEYFRAME }, .flags = FLAGS, .unit = "freq" },
|
||||
{ "keyframe", NULL, 0, AV_OPT_TYPE_CONST, { .i64 = DUMP_FREQ_KEYFRAME }, .flags = FLAGS, .unit = "freq" },
|
||||
{ "e", NULL, 0, AV_OPT_TYPE_CONST, { .i64 = DUMP_FREQ_ALL }, .flags = FLAGS, .unit = "freq" },
|
||||
{ "all", NULL, 0, AV_OPT_TYPE_CONST, { .i64 = DUMP_FREQ_ALL }, .flags = FLAGS, .unit = "freq" },
|
||||
{ NULL },
|
||||
};
|
||||
|
||||
static const AVClass dump_extradata_class = {
|
||||
.class_name = "dump_extradata bsf",
|
||||
.item_name = av_default_item_name,
|
||||
.option = options,
|
||||
.version = LIBAVUTIL_VERSION_INT,
|
||||
};
|
||||
|
||||
const FFBitStreamFilter ff_dump_extradata_bsf = {
|
||||
.p.name = "dump_extra",
|
||||
.p.priv_class = &dump_extradata_class,
|
||||
.priv_data_size = sizeof(DumpExtradataContext),
|
||||
.filter = dump_extradata,
|
||||
};
|
||||
@@ -0,0 +1,138 @@
|
||||
/*
|
||||
* Copyright (c) 2022 Michael Niedermayer
|
||||
*
|
||||
* 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 "bsf.h"
|
||||
#include "bsf_internal.h"
|
||||
#include "libavutil/colorspace.h"
|
||||
#include "libavutil/intreadwrite.h"
|
||||
#include "libavutil/opt.h"
|
||||
|
||||
typedef struct DVErrorMarkerContext {
|
||||
const AVClass *class;
|
||||
uint8_t color_rgba[4];
|
||||
int sta;
|
||||
uint8_t marked_block[76];
|
||||
} DVErrorMarkerContext;
|
||||
|
||||
static void setdc(uint8_t *b, const uint8_t color_rgba[4], int cblocks, int y_step, int v_step, int u_step) {
|
||||
for (int i=0; i<4; i++) {
|
||||
b[0] = RGB_TO_Y_JPEG(color_rgba[0], color_rgba[1],color_rgba[2]) + 128;
|
||||
b[1] = 0x06;
|
||||
b += y_step;
|
||||
}
|
||||
for (int i=0; i<cblocks; i++) {
|
||||
b[0] = RGB_TO_V_JPEG(color_rgba[0], color_rgba[1],color_rgba[2]) - 128;
|
||||
b[1] = 0x16;
|
||||
b += v_step;
|
||||
}
|
||||
for (int i=0; i<cblocks; i++) {
|
||||
b[0] = RGB_TO_U_JPEG(color_rgba[0], color_rgba[1],color_rgba[2]) - 128;
|
||||
b[1] = 0x16;
|
||||
b += u_step;
|
||||
}
|
||||
}
|
||||
|
||||
static int dv_error_marker_init(AVBSFContext *ctx)
|
||||
{
|
||||
DVErrorMarkerContext *s = ctx->priv_data;
|
||||
|
||||
memset(s->marked_block, -1, 76);
|
||||
setdc(s->marked_block, s->color_rgba, 1, 14, 10, 10);
|
||||
setdc(s->marked_block, s->color_rgba, 2, 10, 10, 8);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int dv_error_marker_filter(AVBSFContext *ctx, AVPacket *pkt)
|
||||
{
|
||||
DVErrorMarkerContext *s = ctx->priv_data;
|
||||
int ret = ff_bsf_get_packet_ref(ctx, pkt);
|
||||
uint8_t *p;
|
||||
int writable = 0;
|
||||
int stamask = s->sta;
|
||||
int match_count = 0;
|
||||
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
p = pkt->data;
|
||||
for(int i = 0; i < pkt->size - 79; i+=80) {
|
||||
// see page 44-46 or section 5.5 of http://web.archive.org/web/20060927044735/http://www.smpte.org/smpte_store/standards/pdf/s314m.pdf.
|
||||
if ((p[i] >> 4) == 9 && ((stamask >> (p[i+3] >> 4))&1)) {
|
||||
if (!writable) {
|
||||
ret = av_packet_make_writable(pkt);
|
||||
if (ret < 0) {
|
||||
av_packet_unref(pkt);
|
||||
return ret;
|
||||
}
|
||||
writable = 1;
|
||||
p = pkt->data;
|
||||
}
|
||||
memcpy(p+i+4, s->marked_block, 76);
|
||||
match_count ++;
|
||||
}
|
||||
}
|
||||
av_log(ctx, AV_LOG_DEBUG, "%8"PRId64": Replaced %5d blocks by color %X\n", pkt->pts, match_count, AV_RB32(s->color_rgba));
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
#define OFFSET(x) offsetof(DVErrorMarkerContext, x)
|
||||
#define FLAGS (AV_OPT_FLAG_VIDEO_PARAM|AV_OPT_FLAG_BSF_PARAM)
|
||||
static const AVOption options[] = {
|
||||
{ "color" , "set color", OFFSET(color_rgba), AV_OPT_TYPE_COLOR, {.str = "yellow"}, 0, 0, FLAGS },
|
||||
{ "sta" , "specify which error status value to match"
|
||||
, OFFSET(sta ), AV_OPT_TYPE_FLAGS, {.i64 = 0xFFFE}, 0, 0xFFFF, FLAGS, .unit = "sta" },
|
||||
{ "ok" , "No error, no concealment", 0, AV_OPT_TYPE_CONST, {.i64 = 0x0001}, 0, 0xFFFF, FLAGS, .unit = "sta"},
|
||||
{ "Aa" , "No error, concealment from previous frame type a",0, AV_OPT_TYPE_CONST, {.i64 = 0x0004}, 0, 0xFFFF, FLAGS, .unit = "sta"},
|
||||
{ "Ba" , "No error, concealment from next frame type a", 0, AV_OPT_TYPE_CONST, {.i64 = 0x0010}, 0, 0xFFFF, FLAGS, .unit = "sta"},
|
||||
{ "Ca" , "No error, unspecified concealment type a", 0, AV_OPT_TYPE_CONST, {.i64 = 0x0040}, 0, 0xFFFF, FLAGS, .unit = "sta"},
|
||||
{ "erri" , "Error with inserted code, No concealment", 0, AV_OPT_TYPE_CONST, {.i64 = 0x0080}, 0, 0xFFFF, FLAGS, .unit = "sta"},
|
||||
{ "erru" , "Error with unidentified pos, No concealment", 0, AV_OPT_TYPE_CONST, {.i64 = 0x8000}, 0, 0xFFFF, FLAGS, .unit = "sta"},
|
||||
{ "err" , "Error, No concealment", 0, AV_OPT_TYPE_CONST, {.i64 = 0x8080}, 0, 0xFFFF, FLAGS, .unit = "sta"},
|
||||
{ "Ab" , "No error, concealment from previous frame type b",0, AV_OPT_TYPE_CONST, {.i64 = 0x0400}, 0, 0xFFFF, FLAGS, .unit = "sta"},
|
||||
{ "Bb" , "No error, concealment from next frame type b", 0, AV_OPT_TYPE_CONST, {.i64 = 0x1000}, 0, 0xFFFF, FLAGS, .unit = "sta"},
|
||||
{ "Cb" , "No error, unspecified concealment type b", 0, AV_OPT_TYPE_CONST, {.i64 = 0x4000}, 0, 0xFFFF, FLAGS, .unit = "sta"},
|
||||
{ "A" , "No error, concealment from previous frame", 0, AV_OPT_TYPE_CONST, {.i64 = 0x0404}, 0, 0xFFFF, FLAGS, .unit = "sta"},
|
||||
{ "B" , "No error, concealment from next frame", 0, AV_OPT_TYPE_CONST, {.i64 = 0x1010}, 0, 0xFFFF, FLAGS, .unit = "sta"},
|
||||
{ "C" , "No error, unspecified concealment", 0, AV_OPT_TYPE_CONST, {.i64 = 0x4040}, 0, 0xFFFF, FLAGS, .unit = "sta"},
|
||||
{ "a" , "No error, concealment type a", 0, AV_OPT_TYPE_CONST, {.i64 = 0x0054}, 0, 0xFFFF, FLAGS, .unit = "sta"},
|
||||
{ "b" , "No error, concealment type b", 0, AV_OPT_TYPE_CONST, {.i64 = 0x5400}, 0, 0xFFFF, FLAGS, .unit = "sta"},
|
||||
{ "res" , "Reserved", 0, AV_OPT_TYPE_CONST, {.i64 = 0x2B2A}, 0, 0xFFFF, FLAGS, .unit = "sta"},
|
||||
{ "notok" , "Error or concealment", 0, AV_OPT_TYPE_CONST, {.i64 = 0xD4D4}, 0, 0xFFFF, FLAGS, .unit = "sta"},
|
||||
{ "notres" , "Not reserved", 0, AV_OPT_TYPE_CONST, {.i64 = 0xD4D5}, 0, 0xFFFF, FLAGS, .unit = "sta"},
|
||||
{ NULL },
|
||||
};
|
||||
|
||||
static const AVClass dv_error_marker_class = {
|
||||
.class_name = "dv_error_marker",
|
||||
.item_name = av_default_item_name,
|
||||
.option = options,
|
||||
.version = LIBAVUTIL_VERSION_INT,
|
||||
};
|
||||
|
||||
const FFBitStreamFilter ff_dv_error_marker_bsf = {
|
||||
.p.name = "dv_error_marker",
|
||||
.p.codec_ids = (const enum AVCodecID []){ AV_CODEC_ID_DVVIDEO, AV_CODEC_ID_NONE },
|
||||
.p.priv_class = &dv_error_marker_class,
|
||||
.priv_data_size = sizeof(DVErrorMarkerContext),
|
||||
.init = dv_error_marker_init,
|
||||
.filter = dv_error_marker_filter,
|
||||
};
|
||||
@@ -0,0 +1,86 @@
|
||||
/*
|
||||
* 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 "bsf.h"
|
||||
#include "bsf_internal.h"
|
||||
#include "get_bits.h"
|
||||
#include "ac3_parser_internal.h"
|
||||
|
||||
static int eac3_core_filter(AVBSFContext *ctx, AVPacket *pkt)
|
||||
{
|
||||
AC3HeaderInfo hdr;
|
||||
GetBitContext gbc;
|
||||
int ret;
|
||||
|
||||
ret = ff_bsf_get_packet_ref(ctx, pkt);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
ret = init_get_bits8(&gbc, pkt->data, pkt->size);
|
||||
if (ret < 0)
|
||||
goto fail;
|
||||
|
||||
ret = ff_ac3_parse_header(&gbc, &hdr);
|
||||
if (ret < 0) {
|
||||
ret = AVERROR_INVALIDDATA;
|
||||
goto fail;
|
||||
}
|
||||
|
||||
if (hdr.frame_type == EAC3_FRAME_TYPE_INDEPENDENT ||
|
||||
hdr.frame_type == EAC3_FRAME_TYPE_AC3_CONVERT) {
|
||||
pkt->size = FFMIN(hdr.frame_size, pkt->size);
|
||||
} else if (hdr.frame_type == EAC3_FRAME_TYPE_DEPENDENT && pkt->size > hdr.frame_size) {
|
||||
AC3HeaderInfo hdr2;
|
||||
|
||||
ret = init_get_bits8(&gbc, pkt->data + hdr.frame_size, pkt->size - hdr.frame_size);
|
||||
if (ret < 0)
|
||||
goto fail;
|
||||
|
||||
ret = ff_ac3_parse_header(&gbc, &hdr2);
|
||||
if (ret < 0) {
|
||||
ret = AVERROR_INVALIDDATA;
|
||||
goto fail;
|
||||
}
|
||||
|
||||
if (hdr2.frame_type == EAC3_FRAME_TYPE_INDEPENDENT ||
|
||||
hdr2.frame_type == EAC3_FRAME_TYPE_AC3_CONVERT) {
|
||||
pkt->size -= hdr.frame_size;
|
||||
pkt->data += hdr.frame_size;
|
||||
} else {
|
||||
pkt->size = 0;
|
||||
}
|
||||
} else {
|
||||
pkt->size = 0;
|
||||
}
|
||||
|
||||
return 0;
|
||||
fail:
|
||||
av_packet_unref(pkt);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static const enum AVCodecID codec_ids[] = {
|
||||
AV_CODEC_ID_EAC3, AV_CODEC_ID_NONE,
|
||||
};
|
||||
|
||||
const FFBitStreamFilter ff_eac3_core_bsf = {
|
||||
.p.name = "eac3_core",
|
||||
.p.codec_ids = codec_ids,
|
||||
.filter = eac3_core_filter,
|
||||
};
|
||||
@@ -0,0 +1,277 @@
|
||||
/*
|
||||
* EIA-608 to MXF SMPTE-436M ANC bitstream filter
|
||||
* Copyright (c) 2025 Jacob Lifshay
|
||||
*
|
||||
* 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 "bsf.h"
|
||||
#include "bsf_internal.h"
|
||||
#include "codec_id.h"
|
||||
#include "libavcodec/smpte_436m.h"
|
||||
#include "libavcodec/smpte_436m_internal.h"
|
||||
#include "libavutil/avassert.h"
|
||||
#include "libavutil/avutil.h"
|
||||
#include "libavutil/error.h"
|
||||
#include "libavutil/intreadwrite.h"
|
||||
#include "libavutil/macros.h"
|
||||
#include "libavutil/opt.h"
|
||||
#include "libavutil/rational.h"
|
||||
|
||||
typedef struct EIA608ToSMPTE436MContext {
|
||||
const AVClass *class;
|
||||
unsigned line_number;
|
||||
unsigned cdp_sequence_cntr;
|
||||
unsigned wrapping_type_opt;
|
||||
unsigned sample_coding_opt;
|
||||
AVSmpte436mWrappingType wrapping_type;
|
||||
AVSmpte436mPayloadSampleCoding sample_coding;
|
||||
AVRational cdp_frame_rate;
|
||||
uint8_t cdp_frame_rate_byte;
|
||||
} EIA608ToSMPTE436MContext;
|
||||
|
||||
// clang-format off
|
||||
static const AVSmpte291mAnc8bit test_anc = {
|
||||
.did = 0x61,
|
||||
.sdid_or_dbn = 0x01,
|
||||
.data_count = 0x49,
|
||||
.payload = {
|
||||
// header
|
||||
0x96, 0x69, 0x49, 0x7F, 0x43, 0xFA, 0x8D, 0x72, 0xF4,
|
||||
|
||||
// 608 triples
|
||||
0xFC, 0x80, 0x80, 0xFD, 0x80, 0x80,
|
||||
|
||||
// 708 padding
|
||||
0xFA, 0x00, 0x00, 0xFA, 0x00, 0x00, 0xFA, 0x00, 0x00,
|
||||
0xFA, 0x00, 0x00, 0xFA, 0x00, 0x00, 0xFA, 0x00, 0x00,
|
||||
0xFA, 0x00, 0x00, 0xFA, 0x00, 0x00, 0xFA, 0x00, 0x00,
|
||||
0xFA, 0x00, 0x00, 0xFA, 0x00, 0x00, 0xFA, 0x00, 0x00,
|
||||
0xFA, 0x00, 0x00, 0xFA, 0x00, 0x00, 0xFA, 0x00, 0x00,
|
||||
0xFA, 0x00, 0x00, 0xFA, 0x00, 0x00, 0xFA, 0x00, 0x00,
|
||||
|
||||
// footer
|
||||
0x74, 0xFA, 0x8D, 0x81,
|
||||
},
|
||||
.checksum = 0xAB,
|
||||
};
|
||||
// clang-format on
|
||||
|
||||
static av_cold int ff_eia608_to_smpte436m_init(AVBSFContext *ctx)
|
||||
{
|
||||
EIA608ToSMPTE436MContext *priv = ctx->priv_data;
|
||||
|
||||
priv->wrapping_type = priv->wrapping_type_opt;
|
||||
priv->sample_coding = priv->sample_coding_opt;
|
||||
|
||||
// validate we can handle the selected wrapping type and sample coding
|
||||
|
||||
AVSmpte436mCodedAnc coded_anc;
|
||||
|
||||
int ret = av_smpte_291m_anc_8bit_encode(
|
||||
&coded_anc, priv->line_number, priv->wrapping_type, priv->sample_coding, &test_anc, ctx);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
ctx->par_out->codec_type = AVMEDIA_TYPE_DATA;
|
||||
ctx->par_out->codec_id = AV_CODEC_ID_SMPTE_436M_ANC;
|
||||
|
||||
static const struct {
|
||||
AVRational frame_rate;
|
||||
uint8_t cdp_frame_rate;
|
||||
} known_frame_rates[] = {
|
||||
{ .frame_rate = { .num = 24000, .den = 1001 }, .cdp_frame_rate = 0x1F },
|
||||
{ .frame_rate = { .num = 24, .den = 1 }, .cdp_frame_rate = 0x2F },
|
||||
{ .frame_rate = { .num = 25, .den = 1 }, .cdp_frame_rate = 0x3F },
|
||||
{ .frame_rate = { .num = 30000, .den = 1001 }, .cdp_frame_rate = 0x4F },
|
||||
{ .frame_rate = { .num = 30, .den = 1 }, .cdp_frame_rate = 0x5F },
|
||||
{ .frame_rate = { .num = 50, .den = 1 }, .cdp_frame_rate = 0x6F },
|
||||
{ .frame_rate = { .num = 60000, .den = 1001 }, .cdp_frame_rate = 0x7F },
|
||||
{ .frame_rate = { .num = 60, .den = 1 }, .cdp_frame_rate = 0x8F },
|
||||
};
|
||||
|
||||
priv->cdp_frame_rate_byte = 0;
|
||||
|
||||
for (int i = 0; i < FF_ARRAY_ELEMS(known_frame_rates); i++) {
|
||||
if (known_frame_rates[i].frame_rate.num == priv->cdp_frame_rate.num && known_frame_rates[i].frame_rate.den == priv->cdp_frame_rate.den) {
|
||||
priv->cdp_frame_rate_byte = known_frame_rates[i].cdp_frame_rate;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (priv->cdp_frame_rate_byte == 0) {
|
||||
av_log(ctx,
|
||||
AV_LOG_FATAL,
|
||||
"cdp_frame_rate not supported: %d/%d\n",
|
||||
priv->cdp_frame_rate.num,
|
||||
priv->cdp_frame_rate.den);
|
||||
return AVERROR(EINVAL);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int ff_eia608_to_smpte436m_filter(AVBSFContext *ctx, AVPacket *out)
|
||||
{
|
||||
EIA608ToSMPTE436MContext *priv = ctx->priv_data;
|
||||
AVPacket *in;
|
||||
|
||||
int ret = ff_bsf_get_packet(ctx, &in);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
AVSmpte291mAnc8bit anc;
|
||||
anc.did = 0x61;
|
||||
anc.sdid_or_dbn = 0x1;
|
||||
|
||||
uint8_t *p = anc.payload;
|
||||
|
||||
*p++ = 0x96; // cdp_identifier -- always 0x9669
|
||||
*p++ = 0x69;
|
||||
|
||||
uint8_t *cdp_length_p = p++;
|
||||
|
||||
*p++ = priv->cdp_frame_rate_byte;
|
||||
|
||||
const uint8_t FLAG_CC_DATA_PRESENT = 0x40;
|
||||
const uint8_t FLAG_CAPTION_SERVICE_ACTIVE = 0x2;
|
||||
const uint8_t FLAG_RESERVED = 0x1; // must always be set
|
||||
|
||||
*p++ = FLAG_CC_DATA_PRESENT | FLAG_CAPTION_SERVICE_ACTIVE | FLAG_RESERVED;
|
||||
|
||||
AV_WB16(p, priv->cdp_sequence_cntr);
|
||||
p += 2;
|
||||
|
||||
const uint8_t CC_DATA_SECTION_ID = 0x72;
|
||||
|
||||
*p++ = CC_DATA_SECTION_ID;
|
||||
|
||||
uint8_t *cc_count_p = p++;
|
||||
|
||||
const uint8_t CC_COUNT_MASK = 0x1F;
|
||||
const int CDP_FOOTER_SIZE = 4;
|
||||
|
||||
int cc_count = in->size / 3;
|
||||
int space_left = AV_SMPTE_291M_ANC_PAYLOAD_CAPACITY - (p - anc.payload);
|
||||
int cc_data_space_left = space_left - CDP_FOOTER_SIZE;
|
||||
int max_cc_count = FFMAX(cc_data_space_left / 3, CC_COUNT_MASK);
|
||||
|
||||
if (cc_count > max_cc_count) {
|
||||
av_log(ctx,
|
||||
AV_LOG_ERROR,
|
||||
"cc_count (%d) is bigger than the maximum supported (%d), truncating captions packet\n",
|
||||
cc_count,
|
||||
max_cc_count);
|
||||
cc_count = max_cc_count;
|
||||
}
|
||||
|
||||
*cc_count_p = cc_count | ~CC_COUNT_MASK; // other bits are reserved and set to ones
|
||||
|
||||
for (size_t i = 0; i < cc_count; i++) {
|
||||
size_t start = i * 3;
|
||||
*p++ = in->data[start] | 0xF8; // fill reserved bits with ones
|
||||
*p++ = in->data[start + 1];
|
||||
*p++ = in->data[start + 2];
|
||||
}
|
||||
|
||||
const uint8_t CDP_FOOTER_ID = 0x74;
|
||||
|
||||
*p++ = CDP_FOOTER_ID;
|
||||
|
||||
AV_WB16(p, priv->cdp_sequence_cntr);
|
||||
p += 2;
|
||||
|
||||
uint8_t *packet_checksum_p = p;
|
||||
*p++ = 0;
|
||||
|
||||
anc.data_count = p - anc.payload;
|
||||
*cdp_length_p = anc.data_count;
|
||||
|
||||
int sum = 0;
|
||||
for (int i = 0; i < anc.data_count; i++) {
|
||||
sum += anc.payload[i];
|
||||
}
|
||||
// set to an 8-bit value such that the sum of the bytes of the whole CDP mod 2^8 is 0
|
||||
*packet_checksum_p = -sum;
|
||||
|
||||
priv->cdp_sequence_cntr++;
|
||||
// cdp_sequence_cntr wraps around at 16-bits
|
||||
priv->cdp_sequence_cntr &= 0xFFFFU;
|
||||
|
||||
av_smpte_291m_anc_8bit_fill_checksum(&anc);
|
||||
|
||||
AVSmpte436mCodedAnc coded_anc;
|
||||
ret = av_smpte_291m_anc_8bit_encode(
|
||||
&coded_anc, priv->line_number, (AVSmpte436mWrappingType)priv->wrapping_type, priv->sample_coding, &anc, ctx);
|
||||
if (ret < 0)
|
||||
goto fail;
|
||||
|
||||
ret = av_smpte_436m_anc_encode(NULL, 0, 1, &coded_anc);
|
||||
if (ret < 0)
|
||||
goto fail;
|
||||
|
||||
ret = av_new_packet(out, ret);
|
||||
if (ret < 0)
|
||||
goto fail;
|
||||
|
||||
ret = av_packet_copy_props(out, in);
|
||||
if (ret < 0)
|
||||
goto fail;
|
||||
|
||||
ret = av_smpte_436m_anc_encode(out->data, out->size, 1, &coded_anc);
|
||||
if (ret < 0)
|
||||
goto fail;
|
||||
|
||||
ret = 0;
|
||||
|
||||
fail:
|
||||
if (ret < 0)
|
||||
av_packet_unref(out);
|
||||
av_packet_free(&in);
|
||||
return ret;
|
||||
}
|
||||
|
||||
#define OFFSET(x) offsetof(EIA608ToSMPTE436MContext, x)
|
||||
#define FLAGS AV_OPT_FLAG_BSF_PARAM
|
||||
// clang-format off
|
||||
static const AVOption options[] = {
|
||||
{ "line_number", "line number -- you probably want 9 or 11", OFFSET(line_number), AV_OPT_TYPE_UINT, { .i64 = 9 }, 0, 0xFFFF, FLAGS },
|
||||
{ "wrapping_type", "wrapping type", OFFSET(wrapping_type_opt), AV_OPT_TYPE_UINT, { .i64 = AV_SMPTE_436M_WRAPPING_TYPE_VANC_FRAME }, 0, 0xFF, FLAGS, .unit = "wrapping_type" },
|
||||
FF_SMPTE_436M_WRAPPING_TYPE_VANC_AVOPTIONS(FLAGS, "wrapping_type"),
|
||||
{ "sample_coding", "payload sample coding", OFFSET(sample_coding_opt), AV_OPT_TYPE_UINT, { .i64 = AV_SMPTE_436M_PAYLOAD_SAMPLE_CODING_8BIT_LUMA }, 0, 0xFF, FLAGS, .unit = "sample_coding" },
|
||||
FF_SMPTE_436M_PAYLOAD_SAMPLE_CODING_ANC_AVOPTIONS(FLAGS, "sample_coding"),
|
||||
{ "initial_cdp_sequence_cntr", "initial cdp_*_sequence_cntr value", OFFSET(cdp_sequence_cntr), AV_OPT_TYPE_UINT, { .i64 = 0 }, 0, 0xFFFF, FLAGS },
|
||||
{ "cdp_frame_rate", "set the `cdp_frame_rate` fields", OFFSET(cdp_frame_rate), AV_OPT_TYPE_VIDEO_RATE, { .str = "30000/1001" }, 0, INT_MAX, FLAGS },
|
||||
{ NULL },
|
||||
};
|
||||
// clang-format on
|
||||
|
||||
static const AVClass eia608_to_smpte436m_class = {
|
||||
.class_name = "eia608_to_smpte436m bitstream filter",
|
||||
.item_name = av_default_item_name,
|
||||
.option = options,
|
||||
.version = LIBAVUTIL_VERSION_INT,
|
||||
};
|
||||
|
||||
const FFBitStreamFilter ff_eia608_to_smpte436m_bsf = {
|
||||
.p.name = "eia608_to_smpte436m",
|
||||
.p.codec_ids = (const enum AVCodecID[]){ AV_CODEC_ID_EIA_608, AV_CODEC_ID_NONE },
|
||||
.p.priv_class = &eia608_to_smpte436m_class,
|
||||
.priv_data_size = sizeof(EIA608ToSMPTE436MContext),
|
||||
.init = ff_eia608_to_smpte436m_init,
|
||||
.filter = ff_eia608_to_smpte436m_filter,
|
||||
};
|
||||
@@ -0,0 +1,290 @@
|
||||
/*
|
||||
* Copyright (c) 2019 James Almer <jamrial@gmail.com>
|
||||
*
|
||||
* 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/mem.h"
|
||||
#include "get_bits.h"
|
||||
#include "bsf.h"
|
||||
#include "bsf_internal.h"
|
||||
|
||||
#include "evc.h"
|
||||
#include "evc_parse.h"
|
||||
#include "evc_ps.h"
|
||||
|
||||
// Access unit data
|
||||
typedef struct AccessUnitBuffer {
|
||||
uint8_t *data; // the data buffer
|
||||
size_t data_size; // size of data in bytes
|
||||
unsigned capacity; // buffer capacity
|
||||
} AccessUnitBuffer;
|
||||
|
||||
typedef struct EVCFMergeContext {
|
||||
AVPacket *in, *buffer_pkt;
|
||||
EVCParamSets ps;
|
||||
EVCParserPoc poc;
|
||||
AccessUnitBuffer au_buffer;
|
||||
} EVCFMergeContext;
|
||||
|
||||
static int end_of_access_unit_found(const EVCParamSets *ps, const EVCParserSliceHeader *sh,
|
||||
const EVCParserPoc *poc, enum EVCNALUnitType nalu_type)
|
||||
{
|
||||
EVCParserPPS *pps = ps->pps[sh->slice_pic_parameter_set_id];
|
||||
EVCParserSPS *sps = ps->sps[pps->pps_seq_parameter_set_id];
|
||||
|
||||
av_assert0(sps && pps);
|
||||
|
||||
if (sps->profile_idc == 0) { // BASELINE profile
|
||||
if (nalu_type == EVC_NOIDR_NUT || nalu_type == EVC_IDR_NUT)
|
||||
return 1;
|
||||
} else { // MAIN profile
|
||||
if (nalu_type == EVC_NOIDR_NUT) {
|
||||
if (poc->PicOrderCntVal != poc->prevPicOrderCntVal)
|
||||
return 1;
|
||||
} else if (nalu_type == EVC_IDR_NUT)
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void evc_frame_merge_flush(AVBSFContext *bsf)
|
||||
{
|
||||
EVCFMergeContext *ctx = bsf->priv_data;
|
||||
|
||||
ff_evc_ps_free(&ctx->ps);
|
||||
av_packet_unref(ctx->in);
|
||||
av_packet_unref(ctx->buffer_pkt);
|
||||
ctx->au_buffer.data_size = 0;
|
||||
}
|
||||
|
||||
static int parse_nal_unit(AVBSFContext *bsf, const uint8_t *buf, int buf_size)
|
||||
{
|
||||
EVCFMergeContext *ctx = bsf->priv_data;
|
||||
GetBitContext gb;
|
||||
enum EVCNALUnitType nalu_type;
|
||||
int tid, err;
|
||||
|
||||
err = init_get_bits8(&gb, buf, buf_size);
|
||||
if (err < 0)
|
||||
return err;
|
||||
|
||||
// @see ISO_IEC_23094-1_2020, 7.4.2.2 NAL unit header semantic (Table 4 - NAL unit type codes and NAL unit type classes)
|
||||
// @see enum EVCNALUnitType in evc.h
|
||||
if (get_bits1(&gb)) {// forbidden_zero_bit
|
||||
av_log(bsf, AV_LOG_ERROR, "Invalid NAL unit header\n");
|
||||
return AVERROR_INVALIDDATA;
|
||||
}
|
||||
|
||||
nalu_type = get_bits(&gb, 6) - 1;
|
||||
if (nalu_type < EVC_NOIDR_NUT || nalu_type > EVC_UNSPEC_NUT62) {
|
||||
av_log(bsf, AV_LOG_ERROR, "Invalid NAL unit type: (%d)\n", nalu_type);
|
||||
return AVERROR_INVALIDDATA;
|
||||
}
|
||||
|
||||
tid = get_bits(&gb, 3);
|
||||
skip_bits(&gb, 5); // nuh_reserved_zero_5bits
|
||||
skip_bits1(&gb); // nuh_extension_flag
|
||||
|
||||
switch (nalu_type) {
|
||||
case EVC_SPS_NUT:
|
||||
err = ff_evc_parse_sps(&gb, &ctx->ps);
|
||||
if (err < 0) {
|
||||
av_log(bsf, AV_LOG_ERROR, "SPS parsing error\n");
|
||||
return err;
|
||||
}
|
||||
break;
|
||||
case EVC_PPS_NUT:
|
||||
err = ff_evc_parse_pps(&gb, &ctx->ps);
|
||||
if (err < 0) {
|
||||
av_log(bsf, AV_LOG_ERROR, "PPS parsing error\n");
|
||||
return err;
|
||||
}
|
||||
break;
|
||||
case EVC_IDR_NUT: // Coded slice of a IDR or non-IDR picture
|
||||
case EVC_NOIDR_NUT: {
|
||||
EVCParserSliceHeader sh;
|
||||
|
||||
err = ff_evc_parse_slice_header(&gb, &sh, &ctx->ps, nalu_type);
|
||||
if (err < 0) {
|
||||
av_log(bsf, AV_LOG_ERROR, "Slice header parsing error\n");
|
||||
return err;
|
||||
}
|
||||
|
||||
// POC (picture order count of the current picture) derivation
|
||||
// @see ISO/IEC 23094-1:2020(E) 8.3.1 Decoding process for picture order count
|
||||
err = ff_evc_derive_poc(&ctx->ps, &sh, &ctx->poc, nalu_type, tid);
|
||||
if (err < 0)
|
||||
return err;
|
||||
|
||||
return end_of_access_unit_found(&ctx->ps, &sh, &ctx->poc, nalu_type);
|
||||
|
||||
break;
|
||||
}
|
||||
case EVC_SEI_NUT: // Supplemental Enhancement Information
|
||||
case EVC_APS_NUT: // Adaptation parameter set
|
||||
case EVC_FD_NUT: // Filler data
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int evc_frame_merge_filter(AVBSFContext *bsf, AVPacket *out)
|
||||
{
|
||||
EVCFMergeContext *ctx = bsf->priv_data;
|
||||
AVPacket *in = ctx->in, *buffer_pkt = ctx->buffer_pkt;
|
||||
size_t data_size;
|
||||
int au_end_found = 0, err;
|
||||
|
||||
while (!au_end_found) {
|
||||
uint8_t *buffer;
|
||||
uint32_t nalu_size;
|
||||
|
||||
if (!in->size) {
|
||||
av_packet_unref(in);
|
||||
err = ff_bsf_get_packet_ref(bsf, in);
|
||||
if (err < 0) {
|
||||
if (err == AVERROR_EOF && ctx->au_buffer.data_size > 0)
|
||||
break;
|
||||
return err;
|
||||
}
|
||||
/* Buffer packets with timestamps (there should be at most one per AU)
|
||||
* or any packet if buffer_pkt is empty. The latter is needed to
|
||||
* passthrough positions in case there are no timestamps like with
|
||||
* the raw EVC demuxer. */
|
||||
if (!buffer_pkt->data ||
|
||||
in->pts != AV_NOPTS_VALUE && buffer_pkt->pts == AV_NOPTS_VALUE) {
|
||||
err = av_packet_ref(buffer_pkt, in);
|
||||
if (err < 0)
|
||||
goto end;
|
||||
}
|
||||
}
|
||||
|
||||
// Buffer size is not enough for buffer to store NAL unit 4-bytes prefix (length)
|
||||
if (in->size < EVC_NALU_LENGTH_PREFIX_SIZE)
|
||||
return AVERROR_INVALIDDATA;
|
||||
|
||||
nalu_size = evc_read_nal_unit_length(in->data, EVC_NALU_LENGTH_PREFIX_SIZE, bsf);
|
||||
if (!nalu_size || nalu_size > INT_MAX) {
|
||||
av_log(bsf, AV_LOG_ERROR, "Invalid NAL unit size: (%u)\n", nalu_size);
|
||||
err = AVERROR_INVALIDDATA;
|
||||
goto end;
|
||||
}
|
||||
|
||||
if (in->size < nalu_size + EVC_NALU_LENGTH_PREFIX_SIZE) {
|
||||
err = AVERROR_INVALIDDATA;
|
||||
goto end;
|
||||
}
|
||||
|
||||
err = parse_nal_unit(bsf, in->data + EVC_NALU_LENGTH_PREFIX_SIZE, nalu_size);
|
||||
if (err < 0) {
|
||||
av_log(bsf, AV_LOG_ERROR, "Parsing of NAL unit failed\n");
|
||||
goto end;
|
||||
}
|
||||
au_end_found = err;
|
||||
|
||||
nalu_size += EVC_NALU_LENGTH_PREFIX_SIZE;
|
||||
|
||||
data_size = ctx->au_buffer.data_size + nalu_size;
|
||||
if (data_size > INT_MAX - AV_INPUT_BUFFER_PADDING_SIZE) {
|
||||
av_log(bsf, AV_LOG_ERROR, "Assembled packet is too big\n");
|
||||
err = AVERROR(ERANGE);
|
||||
goto end;
|
||||
}
|
||||
|
||||
buffer = av_fast_realloc(ctx->au_buffer.data, &ctx->au_buffer.capacity,
|
||||
data_size);
|
||||
if (!buffer) {
|
||||
av_freep(&ctx->au_buffer.data);
|
||||
err = AVERROR_INVALIDDATA;
|
||||
goto end;
|
||||
}
|
||||
|
||||
ctx->au_buffer.data = buffer;
|
||||
memcpy(ctx->au_buffer.data + ctx->au_buffer.data_size, in->data, nalu_size);
|
||||
|
||||
ctx->au_buffer.data_size = data_size;
|
||||
|
||||
in->data += nalu_size;
|
||||
in->size -= nalu_size;
|
||||
}
|
||||
|
||||
av_packet_unref(in);
|
||||
data_size = ctx->au_buffer.data_size;
|
||||
|
||||
ctx->au_buffer.data_size = 0;
|
||||
// drop the data in buffer_pkt, if any, but keep the props
|
||||
av_buffer_unref(&buffer_pkt->buf);
|
||||
err = av_buffer_realloc(&buffer_pkt->buf, data_size + AV_INPUT_BUFFER_PADDING_SIZE);
|
||||
if (err < 0)
|
||||
goto end;
|
||||
|
||||
buffer_pkt->data = buffer_pkt->buf->data;
|
||||
buffer_pkt->size = data_size;
|
||||
av_packet_move_ref(out, buffer_pkt);
|
||||
memcpy(out->data, ctx->au_buffer.data, data_size);
|
||||
memset(out->data + data_size, 0, AV_INPUT_BUFFER_PADDING_SIZE);
|
||||
|
||||
err = 0;
|
||||
end:
|
||||
if (err < 0) {
|
||||
av_packet_unref(in);
|
||||
av_packet_unref(buffer_pkt);
|
||||
ctx->au_buffer.data_size = 0;
|
||||
}
|
||||
return err;
|
||||
}
|
||||
|
||||
static int evc_frame_merge_init(AVBSFContext *bsf)
|
||||
{
|
||||
EVCFMergeContext *ctx = bsf->priv_data;
|
||||
|
||||
ctx->in = av_packet_alloc();
|
||||
ctx->buffer_pkt = av_packet_alloc();
|
||||
if (!ctx->in || !ctx->buffer_pkt)
|
||||
return AVERROR(ENOMEM);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void evc_frame_merge_close(AVBSFContext *bsf)
|
||||
{
|
||||
EVCFMergeContext *ctx = bsf->priv_data;
|
||||
|
||||
av_packet_free(&ctx->in);
|
||||
av_packet_free(&ctx->buffer_pkt);
|
||||
ff_evc_ps_free(&ctx->ps);
|
||||
|
||||
ctx->au_buffer.capacity = 0;
|
||||
av_freep(&ctx->au_buffer.data);
|
||||
ctx->au_buffer.data_size = 0;
|
||||
}
|
||||
|
||||
static const enum AVCodecID evc_frame_merge_codec_ids[] = {
|
||||
AV_CODEC_ID_EVC, AV_CODEC_ID_NONE,
|
||||
};
|
||||
|
||||
const FFBitStreamFilter ff_evc_frame_merge_bsf = {
|
||||
.p.name = "evc_frame_merge",
|
||||
.p.codec_ids = evc_frame_merge_codec_ids,
|
||||
.priv_data_size = sizeof(EVCFMergeContext),
|
||||
.init = evc_frame_merge_init,
|
||||
.flush = evc_frame_merge_flush,
|
||||
.close = evc_frame_merge_close,
|
||||
.filter = evc_frame_merge_filter,
|
||||
};
|
||||
@@ -0,0 +1,475 @@
|
||||
/*
|
||||
* 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 <stdint.h>
|
||||
|
||||
#include "libavutil/log.h"
|
||||
#include "libavutil/mem.h"
|
||||
#include "libavutil/opt.h"
|
||||
|
||||
#include "av1.h"
|
||||
#include "av1_parse.h"
|
||||
#include "bsf.h"
|
||||
#include "bsf_internal.h"
|
||||
#include "bytestream.h"
|
||||
#include "h2645_parse.h"
|
||||
#include "h264.h"
|
||||
#include "startcode.h"
|
||||
#include "vc1_common.h"
|
||||
#include "vvc.h"
|
||||
|
||||
#include "hevc/hevc.h"
|
||||
|
||||
typedef struct ExtractExtradataContext {
|
||||
const AVClass *class;
|
||||
|
||||
int (*extract)(AVBSFContext *ctx, AVPacket *pkt,
|
||||
uint8_t **data, int *size);
|
||||
|
||||
/* AV1 specific fields */
|
||||
AV1Packet av1_pkt;
|
||||
|
||||
/* H264/HEVC specific fields */
|
||||
H2645Packet h2645_pkt;
|
||||
|
||||
/* AVOptions */
|
||||
int remove;
|
||||
} ExtractExtradataContext;
|
||||
|
||||
static int val_in_array(const int *arr, size_t len, int val)
|
||||
{
|
||||
for (size_t i = 0; i < len; i++)
|
||||
if (arr[i] == val)
|
||||
return 1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int metadata_is_global(const AV1OBU *obu)
|
||||
{
|
||||
static const int metadata_obu_types[] = {
|
||||
AV1_METADATA_TYPE_HDR_CLL, AV1_METADATA_TYPE_HDR_MDCV,
|
||||
};
|
||||
GetBitContext gb;
|
||||
int metadata_type;
|
||||
|
||||
if (init_get_bits(&gb, obu->data, obu->size_bits) < 0)
|
||||
return 0;
|
||||
|
||||
metadata_type = get_leb(&gb);
|
||||
|
||||
return val_in_array(metadata_obu_types, FF_ARRAY_ELEMS(metadata_obu_types),
|
||||
metadata_type);
|
||||
}
|
||||
|
||||
static int obu_is_global(const AV1OBU *obu)
|
||||
{
|
||||
static const int extradata_obu_types[] = {
|
||||
AV1_OBU_SEQUENCE_HEADER, AV1_OBU_METADATA,
|
||||
};
|
||||
|
||||
if (!val_in_array(extradata_obu_types, FF_ARRAY_ELEMS(extradata_obu_types),
|
||||
obu->type))
|
||||
return 0;
|
||||
if (obu->type != AV1_OBU_METADATA)
|
||||
return 1;
|
||||
|
||||
return metadata_is_global(obu);
|
||||
}
|
||||
|
||||
static int extract_extradata_av1(AVBSFContext *ctx, AVPacket *pkt,
|
||||
uint8_t **data, int *size)
|
||||
{
|
||||
|
||||
ExtractExtradataContext *s = ctx->priv_data;
|
||||
|
||||
int extradata_size = 0, filtered_size = 0;
|
||||
int i, has_seq = 0, ret = 0;
|
||||
|
||||
ret = ff_av1_packet_split(&s->av1_pkt, pkt->data, pkt->size, ctx);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
for (i = 0; i < s->av1_pkt.nb_obus; i++) {
|
||||
AV1OBU *obu = &s->av1_pkt.obus[i];
|
||||
if (obu_is_global(obu)) {
|
||||
extradata_size += obu->raw_size;
|
||||
if (obu->type == AV1_OBU_SEQUENCE_HEADER)
|
||||
has_seq = 1;
|
||||
} else if (s->remove) {
|
||||
filtered_size += obu->raw_size;
|
||||
}
|
||||
}
|
||||
|
||||
if (extradata_size && has_seq) {
|
||||
AVBufferRef *filtered_buf = NULL;
|
||||
PutByteContext pb_filtered_data, pb_extradata;
|
||||
uint8_t *extradata;
|
||||
|
||||
if (s->remove) {
|
||||
filtered_buf = av_buffer_alloc(filtered_size + AV_INPUT_BUFFER_PADDING_SIZE);
|
||||
if (!filtered_buf) {
|
||||
return AVERROR(ENOMEM);
|
||||
}
|
||||
memset(filtered_buf->data + filtered_size, 0, AV_INPUT_BUFFER_PADDING_SIZE);
|
||||
}
|
||||
|
||||
extradata = av_malloc(extradata_size + AV_INPUT_BUFFER_PADDING_SIZE);
|
||||
if (!extradata) {
|
||||
av_buffer_unref(&filtered_buf);
|
||||
return AVERROR(ENOMEM);
|
||||
}
|
||||
|
||||
*data = extradata;
|
||||
*size = extradata_size;
|
||||
|
||||
bytestream2_init_writer(&pb_extradata, extradata, extradata_size);
|
||||
if (s->remove)
|
||||
bytestream2_init_writer(&pb_filtered_data, filtered_buf->data, filtered_size);
|
||||
|
||||
for (i = 0; i < s->av1_pkt.nb_obus; i++) {
|
||||
AV1OBU *obu = &s->av1_pkt.obus[i];
|
||||
if (obu_is_global(obu)) {
|
||||
bytestream2_put_bufferu(&pb_extradata, obu->raw_data, obu->raw_size);
|
||||
} else if (s->remove) {
|
||||
bytestream2_put_bufferu(&pb_filtered_data, obu->raw_data, obu->raw_size);
|
||||
}
|
||||
}
|
||||
|
||||
if (s->remove) {
|
||||
av_buffer_unref(&pkt->buf);
|
||||
pkt->buf = filtered_buf;
|
||||
pkt->data = filtered_buf->data;
|
||||
pkt->size = filtered_size;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int extract_extradata_h2645(AVBSFContext *ctx, AVPacket *pkt,
|
||||
uint8_t **data, int *size)
|
||||
{
|
||||
static const int extradata_nal_types_vvc[] = {
|
||||
VVC_VPS_NUT, VVC_SPS_NUT, VVC_PPS_NUT,
|
||||
};
|
||||
static const int extradata_nal_types_hevc[] = {
|
||||
HEVC_NAL_VPS, HEVC_NAL_SPS, HEVC_NAL_PPS,
|
||||
};
|
||||
static const int extradata_nal_types_h264[] = {
|
||||
H264_NAL_SPS, H264_NAL_PPS,
|
||||
};
|
||||
|
||||
ExtractExtradataContext *s = ctx->priv_data;
|
||||
|
||||
int extradata_size = 0, filtered_size = 0;
|
||||
const int *extradata_nal_types;
|
||||
size_t nb_extradata_nal_types;
|
||||
int i, has_sps = 0, has_vps = 0, ret = 0;
|
||||
|
||||
if (ctx->par_in->codec_id == AV_CODEC_ID_VVC) {
|
||||
extradata_nal_types = extradata_nal_types_vvc;
|
||||
nb_extradata_nal_types = FF_ARRAY_ELEMS(extradata_nal_types_vvc);
|
||||
} else if (ctx->par_in->codec_id == AV_CODEC_ID_HEVC) {
|
||||
extradata_nal_types = extradata_nal_types_hevc;
|
||||
nb_extradata_nal_types = FF_ARRAY_ELEMS(extradata_nal_types_hevc);
|
||||
} else {
|
||||
extradata_nal_types = extradata_nal_types_h264;
|
||||
nb_extradata_nal_types = FF_ARRAY_ELEMS(extradata_nal_types_h264);
|
||||
}
|
||||
|
||||
ret = ff_h2645_packet_split(&s->h2645_pkt, pkt->data, pkt->size,
|
||||
ctx, 0, ctx->par_in->codec_id, H2645_FLAG_SMALL_PADDING);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
for (i = 0; i < s->h2645_pkt.nb_nals; i++) {
|
||||
H2645NAL *nal = &s->h2645_pkt.nals[i];
|
||||
if (val_in_array(extradata_nal_types, nb_extradata_nal_types, nal->type)) {
|
||||
extradata_size += nal->raw_size + 3;
|
||||
if (ctx->par_in->codec_id == AV_CODEC_ID_VVC) {
|
||||
if (nal->type == VVC_SPS_NUT) has_sps = 1;
|
||||
if (nal->type == VVC_VPS_NUT) has_vps = 1;
|
||||
} else if (ctx->par_in->codec_id == AV_CODEC_ID_HEVC) {
|
||||
if (nal->type == HEVC_NAL_SPS) has_sps = 1;
|
||||
if (nal->type == HEVC_NAL_VPS) has_vps = 1;
|
||||
} else {
|
||||
if (nal->type == H264_NAL_SPS) has_sps = 1;
|
||||
}
|
||||
} else if (s->remove) {
|
||||
filtered_size += nal->raw_size + 3;
|
||||
}
|
||||
}
|
||||
|
||||
if (extradata_size &&
|
||||
((ctx->par_in->codec_id == AV_CODEC_ID_VVC && has_sps) ||
|
||||
(ctx->par_in->codec_id == AV_CODEC_ID_HEVC && has_sps && has_vps) ||
|
||||
(ctx->par_in->codec_id == AV_CODEC_ID_H264 && has_sps))) {
|
||||
AVBufferRef *filtered_buf = NULL;
|
||||
PutByteContext pb_filtered_data, pb_extradata;
|
||||
uint8_t *extradata;
|
||||
|
||||
if (s->remove) {
|
||||
filtered_buf = av_buffer_alloc(filtered_size + AV_INPUT_BUFFER_PADDING_SIZE);
|
||||
if (!filtered_buf) {
|
||||
return AVERROR(ENOMEM);
|
||||
}
|
||||
memset(filtered_buf->data + filtered_size, 0, AV_INPUT_BUFFER_PADDING_SIZE);
|
||||
}
|
||||
|
||||
extradata = av_malloc(extradata_size + AV_INPUT_BUFFER_PADDING_SIZE);
|
||||
if (!extradata) {
|
||||
av_buffer_unref(&filtered_buf);
|
||||
return AVERROR(ENOMEM);
|
||||
}
|
||||
|
||||
*data = extradata;
|
||||
*size = extradata_size;
|
||||
|
||||
bytestream2_init_writer(&pb_extradata, extradata, extradata_size);
|
||||
if (s->remove)
|
||||
bytestream2_init_writer(&pb_filtered_data, filtered_buf->data, filtered_size);
|
||||
|
||||
for (i = 0; i < s->h2645_pkt.nb_nals; i++) {
|
||||
H2645NAL *nal = &s->h2645_pkt.nals[i];
|
||||
if (val_in_array(extradata_nal_types, nb_extradata_nal_types,
|
||||
nal->type)) {
|
||||
bytestream2_put_be24u(&pb_extradata, 1); //startcode
|
||||
bytestream2_put_bufferu(&pb_extradata, nal->raw_data, nal->raw_size);
|
||||
} else if (s->remove) {
|
||||
bytestream2_put_be24u(&pb_filtered_data, 1); // startcode
|
||||
bytestream2_put_bufferu(&pb_filtered_data, nal->raw_data, nal->raw_size);
|
||||
}
|
||||
}
|
||||
|
||||
if (s->remove) {
|
||||
av_buffer_unref(&pkt->buf);
|
||||
pkt->buf = filtered_buf;
|
||||
pkt->data = filtered_buf->data;
|
||||
pkt->size = filtered_size;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int extract_extradata_vc1(AVBSFContext *ctx, AVPacket *pkt,
|
||||
uint8_t **data, int *size)
|
||||
{
|
||||
ExtractExtradataContext *s = ctx->priv_data;
|
||||
const uint8_t *ptr = pkt->data, *end = pkt->data + pkt->size;
|
||||
uint32_t state = UINT32_MAX;
|
||||
int has_extradata = 0, extradata_size = 0;
|
||||
|
||||
while (ptr < end) {
|
||||
ptr = avpriv_find_start_code(ptr, end, &state);
|
||||
if (state == VC1_CODE_SEQHDR || state == VC1_CODE_ENTRYPOINT) {
|
||||
has_extradata = 1;
|
||||
} else if (has_extradata && IS_MARKER(state)) {
|
||||
extradata_size = ptr - 4 - pkt->data;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (extradata_size) {
|
||||
*data = av_malloc(extradata_size + AV_INPUT_BUFFER_PADDING_SIZE);
|
||||
if (!*data)
|
||||
return AVERROR(ENOMEM);
|
||||
|
||||
memcpy(*data, pkt->data, extradata_size);
|
||||
*size = extradata_size;
|
||||
|
||||
if (s->remove) {
|
||||
pkt->data += extradata_size;
|
||||
pkt->size -= extradata_size;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int extract_extradata_mpeg12(AVBSFContext *ctx, AVPacket *pkt,
|
||||
uint8_t **data, int *size)
|
||||
{
|
||||
ExtractExtradataContext *s = ctx->priv_data;
|
||||
uint32_t state = UINT32_MAX;
|
||||
int i, found = 0;
|
||||
|
||||
for (i = 0; i < pkt->size; i++) {
|
||||
state = (state << 8) | pkt->data[i];
|
||||
if (state == 0x1B3)
|
||||
found = 1;
|
||||
else if (found && state != 0x1B5 && state < 0x200 && state >= 0x100) {
|
||||
*size = i - 3;
|
||||
*data = av_malloc(*size + AV_INPUT_BUFFER_PADDING_SIZE);
|
||||
if (!*data)
|
||||
return AVERROR(ENOMEM);
|
||||
|
||||
memcpy(*data, pkt->data, *size);
|
||||
|
||||
if (s->remove) {
|
||||
pkt->data += *size;
|
||||
pkt->size -= *size;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int extract_extradata_mpeg4(AVBSFContext *ctx, AVPacket *pkt,
|
||||
uint8_t **data, int *size)
|
||||
{
|
||||
ExtractExtradataContext *s = ctx->priv_data;
|
||||
const uint8_t *ptr = pkt->data, *end = pkt->data + pkt->size;
|
||||
uint32_t state = UINT32_MAX;
|
||||
|
||||
while (ptr < end) {
|
||||
ptr = avpriv_find_start_code(ptr, end, &state);
|
||||
if (state == 0x1B3 || state == 0x1B6) {
|
||||
if (ptr - pkt->data > 4) {
|
||||
*size = ptr - 4 - pkt->data;
|
||||
*data = av_malloc(*size + AV_INPUT_BUFFER_PADDING_SIZE);
|
||||
if (!*data)
|
||||
return AVERROR(ENOMEM);
|
||||
|
||||
memcpy(*data, pkt->data, *size);
|
||||
|
||||
if (s->remove) {
|
||||
pkt->data += *size;
|
||||
pkt->size -= *size;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct {
|
||||
enum AVCodecID id;
|
||||
int (*extract)(AVBSFContext *ctx, AVPacket *pkt,
|
||||
uint8_t **data, int *size);
|
||||
} extract_tab[] = {
|
||||
{ AV_CODEC_ID_AV1, extract_extradata_av1 },
|
||||
{ AV_CODEC_ID_AVS2, extract_extradata_mpeg4 },
|
||||
{ AV_CODEC_ID_AVS3, extract_extradata_mpeg4 },
|
||||
{ AV_CODEC_ID_CAVS, extract_extradata_mpeg4 },
|
||||
{ AV_CODEC_ID_H264, extract_extradata_h2645 },
|
||||
{ AV_CODEC_ID_HEVC, extract_extradata_h2645 },
|
||||
{ AV_CODEC_ID_MPEG1VIDEO, extract_extradata_mpeg12 },
|
||||
{ AV_CODEC_ID_MPEG2VIDEO, extract_extradata_mpeg12 },
|
||||
{ AV_CODEC_ID_MPEG4, extract_extradata_mpeg4 },
|
||||
{ AV_CODEC_ID_VC1, extract_extradata_vc1 },
|
||||
{ AV_CODEC_ID_VVC, extract_extradata_h2645 },
|
||||
};
|
||||
|
||||
static int extract_extradata_init(AVBSFContext *ctx)
|
||||
{
|
||||
ExtractExtradataContext *s = ctx->priv_data;
|
||||
int i;
|
||||
|
||||
for (i = 0; i < FF_ARRAY_ELEMS(extract_tab); i++) {
|
||||
if (extract_tab[i].id == ctx->par_in->codec_id) {
|
||||
s->extract = extract_tab[i].extract;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!s->extract)
|
||||
return AVERROR_BUG;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int extract_extradata_filter(AVBSFContext *ctx, AVPacket *pkt)
|
||||
{
|
||||
ExtractExtradataContext *s = ctx->priv_data;
|
||||
uint8_t *extradata = NULL;
|
||||
int extradata_size;
|
||||
int ret = 0;
|
||||
|
||||
ret = ff_bsf_get_packet_ref(ctx, pkt);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
ret = s->extract(ctx, pkt, &extradata, &extradata_size);
|
||||
if (ret < 0)
|
||||
goto fail;
|
||||
|
||||
if (extradata) {
|
||||
memset(extradata + extradata_size, 0, AV_INPUT_BUFFER_PADDING_SIZE);
|
||||
ret = av_packet_add_side_data(pkt, AV_PKT_DATA_NEW_EXTRADATA,
|
||||
extradata, extradata_size);
|
||||
if (ret < 0) {
|
||||
av_freep(&extradata);
|
||||
goto fail;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
fail:
|
||||
av_packet_unref(pkt);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void extract_extradata_close(AVBSFContext *ctx)
|
||||
{
|
||||
ExtractExtradataContext *s = ctx->priv_data;
|
||||
ff_av1_packet_uninit(&s->av1_pkt);
|
||||
ff_h2645_packet_uninit(&s->h2645_pkt);
|
||||
}
|
||||
|
||||
static const enum AVCodecID codec_ids[] = {
|
||||
AV_CODEC_ID_AV1,
|
||||
AV_CODEC_ID_AVS2,
|
||||
AV_CODEC_ID_AVS3,
|
||||
AV_CODEC_ID_CAVS,
|
||||
AV_CODEC_ID_H264,
|
||||
AV_CODEC_ID_HEVC,
|
||||
AV_CODEC_ID_MPEG1VIDEO,
|
||||
AV_CODEC_ID_MPEG2VIDEO,
|
||||
AV_CODEC_ID_MPEG4,
|
||||
AV_CODEC_ID_VC1,
|
||||
AV_CODEC_ID_VVC,
|
||||
AV_CODEC_ID_NONE,
|
||||
};
|
||||
|
||||
#define OFFSET(x) offsetof(ExtractExtradataContext, x)
|
||||
#define FLAGS (AV_OPT_FLAG_VIDEO_PARAM|AV_OPT_FLAG_BSF_PARAM)
|
||||
static const AVOption options[] = {
|
||||
{ "remove", "remove the extradata from the bitstream", OFFSET(remove), AV_OPT_TYPE_INT,
|
||||
{ .i64 = 0 }, 0, 1, FLAGS },
|
||||
{ NULL },
|
||||
};
|
||||
|
||||
static const AVClass extract_extradata_class = {
|
||||
.class_name = "extract_extradata",
|
||||
.item_name = av_default_item_name,
|
||||
.option = options,
|
||||
.version = LIBAVUTIL_VERSION_INT,
|
||||
};
|
||||
|
||||
const FFBitStreamFilter ff_extract_extradata_bsf = {
|
||||
.p.name = "extract_extradata",
|
||||
.p.codec_ids = codec_ids,
|
||||
.p.priv_class = &extract_extradata_class,
|
||||
.priv_data_size = sizeof(ExtractExtradataContext),
|
||||
.init = extract_extradata_init,
|
||||
.filter = extract_extradata_filter,
|
||||
.close = extract_extradata_close,
|
||||
};
|
||||
@@ -0,0 +1,287 @@
|
||||
/*
|
||||
* 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 <stdbool.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#include "libavutil/mem.h"
|
||||
#include "libavutil/opt.h"
|
||||
|
||||
#include "bsf.h"
|
||||
#include "bsf_internal.h"
|
||||
#include "cbs.h"
|
||||
|
||||
|
||||
typedef struct FilterUnitsContext {
|
||||
const AVClass *class;
|
||||
|
||||
CodedBitstreamContext *cbc;
|
||||
CodedBitstreamFragment fragment;
|
||||
|
||||
const char *pass_types;
|
||||
const char *remove_types;
|
||||
enum AVDiscard discard;
|
||||
int discard_flags;
|
||||
|
||||
enum {
|
||||
NOOP,
|
||||
PASS,
|
||||
REMOVE,
|
||||
} mode;
|
||||
CodedBitstreamUnitType *type_list;
|
||||
int nb_types;
|
||||
bool passthrough;
|
||||
} FilterUnitsContext;
|
||||
|
||||
|
||||
static int filter_units_make_type_list(const char *list_string,
|
||||
CodedBitstreamUnitType **type_list,
|
||||
int *nb_types)
|
||||
{
|
||||
CodedBitstreamUnitType *list = NULL;
|
||||
int pass, count;
|
||||
|
||||
for (pass = 1; pass <= 2; pass++) {
|
||||
long value, range_start, range_end;
|
||||
const char *str;
|
||||
char *value_end;
|
||||
|
||||
count = 0;
|
||||
for (str = list_string; *str;) {
|
||||
value = strtol(str, &value_end, 0);
|
||||
if (str == value_end)
|
||||
goto invalid;
|
||||
str = (const char *)value_end;
|
||||
if (*str == '-') {
|
||||
++str;
|
||||
range_start = value;
|
||||
range_end = strtol(str, &value_end, 0);
|
||||
if (str == value_end)
|
||||
goto invalid;
|
||||
|
||||
for (value = range_start; value < range_end; value++) {
|
||||
if (pass == 2)
|
||||
list[count] = value;
|
||||
++count;
|
||||
}
|
||||
} else {
|
||||
if (pass == 2)
|
||||
list[count] = value;
|
||||
++count;
|
||||
}
|
||||
if (*str == '|')
|
||||
++str;
|
||||
}
|
||||
if (pass == 1) {
|
||||
list = av_malloc_array(count, sizeof(*list));
|
||||
if (!list)
|
||||
return AVERROR(ENOMEM);
|
||||
}
|
||||
}
|
||||
|
||||
*type_list = list;
|
||||
*nb_types = count;
|
||||
return 0;
|
||||
|
||||
invalid:
|
||||
av_freep(&list);
|
||||
return AVERROR(EINVAL);
|
||||
}
|
||||
|
||||
static int filter_units_filter(AVBSFContext *bsf, AVPacket *pkt)
|
||||
{
|
||||
FilterUnitsContext *ctx = bsf->priv_data;
|
||||
CodedBitstreamFragment *frag = &ctx->fragment;
|
||||
int err, i, j;
|
||||
|
||||
err = ff_bsf_get_packet_ref(bsf, pkt);
|
||||
if (err < 0)
|
||||
return err;
|
||||
|
||||
if (ctx->passthrough)
|
||||
return 0;
|
||||
|
||||
err = ff_cbs_read_packet(ctx->cbc, frag, pkt);
|
||||
if (err < 0) {
|
||||
av_log(bsf, AV_LOG_ERROR, "Failed to read packet.\n");
|
||||
goto fail;
|
||||
}
|
||||
|
||||
ff_cbs_discard_units(ctx->cbc, frag, ctx->discard, ctx->discard_flags);
|
||||
if (ctx->mode != NOOP) {
|
||||
for (i = frag->nb_units - 1; i >= 0; i--) {
|
||||
for (j = 0; j < ctx->nb_types; j++) {
|
||||
if (frag->units[i].type == ctx->type_list[j])
|
||||
break;
|
||||
}
|
||||
if (ctx->mode == REMOVE ? j < ctx->nb_types
|
||||
: j >= ctx->nb_types)
|
||||
ff_cbs_delete_unit(frag, i);
|
||||
}
|
||||
}
|
||||
|
||||
if (frag->nb_units == 0) {
|
||||
// Don't return packets with nothing in them.
|
||||
err = AVERROR(EAGAIN);
|
||||
goto fail;
|
||||
}
|
||||
|
||||
err = ff_cbs_write_packet(ctx->cbc, pkt, frag);
|
||||
if (err < 0) {
|
||||
av_log(bsf, AV_LOG_ERROR, "Failed to write packet.\n");
|
||||
goto fail;
|
||||
}
|
||||
|
||||
fail:
|
||||
if (err < 0)
|
||||
av_packet_unref(pkt);
|
||||
ff_cbs_fragment_reset(frag);
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
static int filter_units_init(AVBSFContext *bsf)
|
||||
{
|
||||
FilterUnitsContext *ctx = bsf->priv_data;
|
||||
int err;
|
||||
|
||||
if (ctx->pass_types && ctx->remove_types) {
|
||||
av_log(bsf, AV_LOG_ERROR, "Exactly one of pass_types or "
|
||||
"remove_types is required.\n");
|
||||
return AVERROR(EINVAL);
|
||||
}
|
||||
|
||||
if (ctx->pass_types) {
|
||||
ctx->mode = PASS;
|
||||
err = filter_units_make_type_list(ctx->pass_types,
|
||||
&ctx->type_list, &ctx->nb_types);
|
||||
if (err < 0) {
|
||||
av_log(bsf, AV_LOG_ERROR, "Failed to parse pass_types.\n");
|
||||
return err;
|
||||
}
|
||||
} else if (ctx->remove_types) {
|
||||
ctx->mode = REMOVE;
|
||||
err = filter_units_make_type_list(ctx->remove_types,
|
||||
&ctx->type_list, &ctx->nb_types);
|
||||
if (err < 0) {
|
||||
av_log(bsf, AV_LOG_ERROR, "Failed to parse remove_types.\n");
|
||||
return err;
|
||||
}
|
||||
} else if (ctx->discard == AVDISCARD_NONE) {
|
||||
ctx->passthrough = true;
|
||||
return 0;
|
||||
}
|
||||
|
||||
err = ff_cbs_init(&ctx->cbc, bsf->par_in->codec_id, bsf);
|
||||
if (err < 0)
|
||||
return err;
|
||||
|
||||
if (ctx->discard == AVDISCARD_NONE) {
|
||||
// Don't actually decompose anything, we only want the unit data.
|
||||
ctx->cbc->decompose_unit_types = ctx->type_list;
|
||||
ctx->cbc->nb_decompose_unit_types = 0;
|
||||
}
|
||||
|
||||
if (bsf->par_in->extradata) {
|
||||
CodedBitstreamFragment *frag = &ctx->fragment;
|
||||
|
||||
err = ff_cbs_read_extradata(ctx->cbc, frag, bsf->par_in);
|
||||
if (err < 0) {
|
||||
av_log(bsf, AV_LOG_ERROR, "Failed to read extradata.\n");
|
||||
} else {
|
||||
err = ff_cbs_write_extradata(ctx->cbc, bsf->par_out, frag);
|
||||
if (err < 0)
|
||||
av_log(bsf, AV_LOG_ERROR, "Failed to write extradata.\n");
|
||||
}
|
||||
|
||||
ff_cbs_fragment_reset(frag);
|
||||
}
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
static void filter_units_close(AVBSFContext *bsf)
|
||||
{
|
||||
FilterUnitsContext *ctx = bsf->priv_data;
|
||||
|
||||
av_freep(&ctx->type_list);
|
||||
|
||||
ff_cbs_fragment_free(&ctx->fragment);
|
||||
ff_cbs_close(&ctx->cbc);
|
||||
}
|
||||
|
||||
#define OFFSET(x) offsetof(FilterUnitsContext, x)
|
||||
#define FLAGS (AV_OPT_FLAG_VIDEO_PARAM|AV_OPT_FLAG_BSF_PARAM)
|
||||
static const AVOption filter_units_options[] = {
|
||||
{ "pass_types", "List of unit types to pass through the filter.",
|
||||
OFFSET(pass_types), AV_OPT_TYPE_STRING,
|
||||
{ .str = NULL }, .flags = FLAGS },
|
||||
{ "remove_types", "List of unit types to remove in the filter.",
|
||||
OFFSET(remove_types), AV_OPT_TYPE_STRING,
|
||||
{ .str = NULL }, .flags = FLAGS },
|
||||
|
||||
{ "discard", "Remove the selected frames",
|
||||
OFFSET(discard), AV_OPT_TYPE_INT,
|
||||
{ .i64 = AVDISCARD_NONE }, INT_MIN, INT_MAX, FLAGS, .unit = "discard"},
|
||||
{ "none" , "discard none",
|
||||
0, AV_OPT_TYPE_CONST,
|
||||
{ .i64 = AVDISCARD_NONE }, INT_MIN, INT_MAX, FLAGS, .unit = "discard"},
|
||||
{ "default" , "discard none, but can be changed after dynamically",
|
||||
0, AV_OPT_TYPE_CONST,
|
||||
{ .i64 = AVDISCARD_DEFAULT }, INT_MIN, INT_MAX, FLAGS, .unit = "discard"},
|
||||
{ "nonref", "discard all non-reference frames",
|
||||
0, AV_OPT_TYPE_CONST,
|
||||
{ .i64 = AVDISCARD_NONREF }, INT_MIN, INT_MAX, FLAGS, .unit = "discard"},
|
||||
{ "bidir", "discard all bidirectional frames",
|
||||
0, AV_OPT_TYPE_CONST,
|
||||
{ .i64 = AVDISCARD_BIDIR }, INT_MIN, INT_MAX, FLAGS, .unit = "discard"},
|
||||
{ "nonintra", "discard all frames except I frames",
|
||||
0, AV_OPT_TYPE_CONST,
|
||||
{ .i64 = AVDISCARD_NONINTRA }, INT_MIN, INT_MAX, FLAGS, .unit = "discard"},
|
||||
{ "nonkey", "discard all frames except keyframes",
|
||||
0, AV_OPT_TYPE_CONST,
|
||||
{ .i64 = AVDISCARD_NONKEY }, INT_MIN, INT_MAX, FLAGS, .unit = "discard"},
|
||||
{ "all", "discard all frames",
|
||||
0, AV_OPT_TYPE_CONST,
|
||||
{ .i64 = AVDISCARD_ALL }, INT_MIN, INT_MAX, FLAGS, .unit = "discard"},
|
||||
|
||||
{ "discard_flags", "flags to control the discard frame behavior",
|
||||
OFFSET(discard_flags), AV_OPT_TYPE_FLAGS,
|
||||
{ .i64 = DISCARD_FLAG_NONE }, INT_MIN, INT_MAX, FLAGS, .unit = "discard_flags"},
|
||||
{ "keep_non_vcl", "non-vcl units even if the picture has been dropped",
|
||||
0, AV_OPT_TYPE_CONST,
|
||||
{ .i64 = DISCARD_FLAG_KEEP_NON_VCL }, INT_MIN, INT_MAX, FLAGS, .unit = "discard_flags"},
|
||||
{ NULL }
|
||||
};
|
||||
|
||||
static const AVClass filter_units_class = {
|
||||
.class_name = "filter_units",
|
||||
.item_name = av_default_item_name,
|
||||
.option = filter_units_options,
|
||||
.version = LIBAVUTIL_VERSION_INT,
|
||||
};
|
||||
|
||||
const FFBitStreamFilter ff_filter_units_bsf = {
|
||||
.p.name = "filter_units",
|
||||
.p.codec_ids = ff_cbs_all_codec_ids,
|
||||
.p.priv_class = &filter_units_class,
|
||||
.priv_data_size = sizeof(FilterUnitsContext),
|
||||
.init = &filter_units_init,
|
||||
.close = &filter_units_close,
|
||||
.filter = &filter_units_filter,
|
||||
};
|
||||
@@ -0,0 +1,724 @@
|
||||
/*
|
||||
* 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/avstring.h"
|
||||
#include "libavutil/display.h"
|
||||
#include "libavutil/common.h"
|
||||
#include "libavutil/mem.h"
|
||||
#include "libavutil/opt.h"
|
||||
|
||||
#include "bsf.h"
|
||||
#include "bsf_internal.h"
|
||||
#include "cbs.h"
|
||||
#include "cbs_bsf.h"
|
||||
#include "cbs_h264.h"
|
||||
#include "cbs_sei.h"
|
||||
#include "h264.h"
|
||||
#include "h264_levels.h"
|
||||
#include "h2645data.h"
|
||||
#include "sei.h"
|
||||
|
||||
enum {
|
||||
FLIP_HORIZONTAL = 1,
|
||||
FLIP_VERTICAL = 2,
|
||||
};
|
||||
|
||||
enum {
|
||||
LEVEL_UNSET = -2,
|
||||
LEVEL_AUTO = -1,
|
||||
};
|
||||
|
||||
typedef struct H264MetadataContext {
|
||||
CBSBSFContext common;
|
||||
|
||||
int done_first_au;
|
||||
|
||||
int aud;
|
||||
H264RawAUD aud_nal;
|
||||
|
||||
AVRational sample_aspect_ratio;
|
||||
|
||||
int overscan_appropriate_flag;
|
||||
|
||||
int video_format;
|
||||
int video_full_range_flag;
|
||||
int colour_primaries;
|
||||
int transfer_characteristics;
|
||||
int matrix_coefficients;
|
||||
|
||||
int chroma_sample_loc_type;
|
||||
|
||||
AVRational tick_rate;
|
||||
int fixed_frame_rate_flag;
|
||||
int zero_new_constraint_set_flags;
|
||||
|
||||
int crop_left;
|
||||
int crop_right;
|
||||
int crop_top;
|
||||
int crop_bottom;
|
||||
|
||||
const char *sei_user_data;
|
||||
SEIRawUserDataUnregistered sei_user_data_payload;
|
||||
|
||||
int delete_filler;
|
||||
|
||||
int display_orientation;
|
||||
double rotate;
|
||||
int flip;
|
||||
H264RawSEIDisplayOrientation display_orientation_payload;
|
||||
|
||||
int level;
|
||||
} H264MetadataContext;
|
||||
|
||||
|
||||
static int h264_metadata_insert_aud(AVBSFContext *bsf,
|
||||
CodedBitstreamFragment *au)
|
||||
{
|
||||
H264MetadataContext *ctx = bsf->priv_data;
|
||||
int primary_pic_type_mask = 0xff;
|
||||
int err, i, j;
|
||||
|
||||
static const int primary_pic_type_table[] = {
|
||||
0x084, // 2, 7
|
||||
0x0a5, // 0, 2, 5, 7
|
||||
0x0e7, // 0, 1, 2, 5, 6, 7
|
||||
0x210, // 4, 9
|
||||
0x318, // 3, 4, 8, 9
|
||||
0x294, // 2, 4, 7, 9
|
||||
0x3bd, // 0, 2, 3, 4, 5, 7, 8, 9
|
||||
0x3ff, // 0, 1, 2, 3, 4, 5, 6, 7, 8, 9
|
||||
};
|
||||
|
||||
for (i = 0; i < au->nb_units; i++) {
|
||||
if (au->units[i].type == H264_NAL_SLICE ||
|
||||
au->units[i].type == H264_NAL_IDR_SLICE) {
|
||||
H264RawSlice *slice = au->units[i].content;
|
||||
for (j = 0; j < FF_ARRAY_ELEMS(primary_pic_type_table); j++) {
|
||||
if (!(primary_pic_type_table[j] &
|
||||
(1 << slice->header.slice_type)))
|
||||
primary_pic_type_mask &= ~(1 << j);
|
||||
}
|
||||
}
|
||||
}
|
||||
for (j = 0; j < FF_ARRAY_ELEMS(primary_pic_type_table); j++)
|
||||
if (primary_pic_type_mask & (1 << j))
|
||||
break;
|
||||
if (j >= FF_ARRAY_ELEMS(primary_pic_type_table)) {
|
||||
av_log(bsf, AV_LOG_ERROR, "No usable primary_pic_type: "
|
||||
"invalid slice types?\n");
|
||||
return AVERROR_INVALIDDATA;
|
||||
}
|
||||
|
||||
ctx->aud_nal = (H264RawAUD) {
|
||||
.nal_unit_header.nal_unit_type = H264_NAL_AUD,
|
||||
.primary_pic_type = j,
|
||||
};
|
||||
|
||||
err = ff_cbs_insert_unit_content(au, 0, H264_NAL_AUD,
|
||||
&ctx->aud_nal, NULL);
|
||||
if (err < 0) {
|
||||
av_log(bsf, AV_LOG_ERROR, "Failed to insert AUD.\n");
|
||||
return err;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int h264_metadata_update_sps(AVBSFContext *bsf,
|
||||
H264RawSPS *sps)
|
||||
{
|
||||
H264MetadataContext *ctx = bsf->priv_data;
|
||||
int need_vui = 0;
|
||||
int crop_unit_x, crop_unit_y;
|
||||
|
||||
if (ctx->sample_aspect_ratio.num && ctx->sample_aspect_ratio.den) {
|
||||
int num, den, i;
|
||||
|
||||
av_reduce(&num, &den, ctx->sample_aspect_ratio.num,
|
||||
ctx->sample_aspect_ratio.den, 65535);
|
||||
|
||||
for (i = 1; i < FF_ARRAY_ELEMS(ff_h2645_pixel_aspect); i++) {
|
||||
if (num == ff_h2645_pixel_aspect[i].num &&
|
||||
den == ff_h2645_pixel_aspect[i].den)
|
||||
break;
|
||||
}
|
||||
if (i == FF_ARRAY_ELEMS(ff_h2645_pixel_aspect)) {
|
||||
sps->vui.aspect_ratio_idc = 255;
|
||||
sps->vui.sar_width = num;
|
||||
sps->vui.sar_height = den;
|
||||
} else {
|
||||
sps->vui.aspect_ratio_idc = i;
|
||||
}
|
||||
sps->vui.aspect_ratio_info_present_flag = 1;
|
||||
need_vui = 1;
|
||||
}
|
||||
|
||||
#define SET_VUI_FIELD(field) do { \
|
||||
if (ctx->field >= 0) { \
|
||||
sps->vui.field = ctx->field; \
|
||||
need_vui = 1; \
|
||||
} \
|
||||
} while (0)
|
||||
|
||||
if (ctx->overscan_appropriate_flag >= 0) {
|
||||
SET_VUI_FIELD(overscan_appropriate_flag);
|
||||
sps->vui.overscan_info_present_flag = 1;
|
||||
}
|
||||
|
||||
if (ctx->video_format >= 0 ||
|
||||
ctx->video_full_range_flag >= 0 ||
|
||||
ctx->colour_primaries >= 0 ||
|
||||
ctx->transfer_characteristics >= 0 ||
|
||||
ctx->matrix_coefficients >= 0) {
|
||||
|
||||
SET_VUI_FIELD(video_format);
|
||||
|
||||
SET_VUI_FIELD(video_full_range_flag);
|
||||
|
||||
if (ctx->colour_primaries >= 0 ||
|
||||
ctx->transfer_characteristics >= 0 ||
|
||||
ctx->matrix_coefficients >= 0) {
|
||||
|
||||
SET_VUI_FIELD(colour_primaries);
|
||||
SET_VUI_FIELD(transfer_characteristics);
|
||||
SET_VUI_FIELD(matrix_coefficients);
|
||||
|
||||
sps->vui.colour_description_present_flag = 1;
|
||||
}
|
||||
sps->vui.video_signal_type_present_flag = 1;
|
||||
}
|
||||
|
||||
if (ctx->chroma_sample_loc_type >= 0) {
|
||||
sps->vui.chroma_sample_loc_type_top_field =
|
||||
ctx->chroma_sample_loc_type;
|
||||
sps->vui.chroma_sample_loc_type_bottom_field =
|
||||
ctx->chroma_sample_loc_type;
|
||||
sps->vui.chroma_loc_info_present_flag = 1;
|
||||
need_vui = 1;
|
||||
}
|
||||
|
||||
if (ctx->tick_rate.num && ctx->tick_rate.den) {
|
||||
int num, den;
|
||||
|
||||
av_reduce(&num, &den, ctx->tick_rate.num, ctx->tick_rate.den,
|
||||
UINT32_MAX > INT_MAX ? UINT32_MAX : INT_MAX);
|
||||
|
||||
sps->vui.time_scale = num;
|
||||
sps->vui.num_units_in_tick = den;
|
||||
|
||||
sps->vui.timing_info_present_flag = 1;
|
||||
need_vui = 1;
|
||||
}
|
||||
SET_VUI_FIELD(fixed_frame_rate_flag);
|
||||
if (ctx->zero_new_constraint_set_flags) {
|
||||
sps->constraint_set4_flag = 0;
|
||||
sps->constraint_set5_flag = 0;
|
||||
}
|
||||
|
||||
if (sps->separate_colour_plane_flag || sps->chroma_format_idc == 0) {
|
||||
crop_unit_x = 1;
|
||||
crop_unit_y = 2 - sps->frame_mbs_only_flag;
|
||||
} else {
|
||||
crop_unit_x = 1 + (sps->chroma_format_idc < 3);
|
||||
crop_unit_y = (1 + (sps->chroma_format_idc < 2)) *
|
||||
(2 - sps->frame_mbs_only_flag);
|
||||
}
|
||||
#define CROP(border, unit) do { \
|
||||
if (ctx->crop_ ## border >= 0) { \
|
||||
if (ctx->crop_ ## border % unit != 0) { \
|
||||
av_log(bsf, AV_LOG_ERROR, "Invalid value for crop_%s: " \
|
||||
"must be a multiple of %d.\n", #border, unit); \
|
||||
return AVERROR(EINVAL); \
|
||||
} \
|
||||
sps->frame_crop_ ## border ## _offset = \
|
||||
ctx->crop_ ## border / unit; \
|
||||
sps->frame_cropping_flag = 1; \
|
||||
} \
|
||||
} while (0)
|
||||
CROP(left, crop_unit_x);
|
||||
CROP(right, crop_unit_x);
|
||||
CROP(top, crop_unit_y);
|
||||
CROP(bottom, crop_unit_y);
|
||||
#undef CROP
|
||||
|
||||
if (ctx->level != LEVEL_UNSET) {
|
||||
int level_idc;
|
||||
|
||||
if (ctx->level == LEVEL_AUTO) {
|
||||
const H264LevelDescriptor *desc;
|
||||
int64_t bit_rate;
|
||||
int width, height, dpb_frames;
|
||||
int framerate;
|
||||
|
||||
if (sps->vui.nal_hrd_parameters_present_flag) {
|
||||
bit_rate = (sps->vui.nal_hrd_parameters.bit_rate_value_minus1[0] + 1) *
|
||||
(INT64_C(1) << (sps->vui.nal_hrd_parameters.bit_rate_scale + 6));
|
||||
} else if (sps->vui.vcl_hrd_parameters_present_flag) {
|
||||
bit_rate = (sps->vui.vcl_hrd_parameters.bit_rate_value_minus1[0] + 1) *
|
||||
(INT64_C(1) << (sps->vui.vcl_hrd_parameters.bit_rate_scale + 6));
|
||||
// Adjust for VCL vs. NAL limits.
|
||||
bit_rate = bit_rate * 6 / 5;
|
||||
} else {
|
||||
bit_rate = 0;
|
||||
}
|
||||
|
||||
// Don't use max_dec_frame_buffering if it is only inferred.
|
||||
dpb_frames = sps->vui.bitstream_restriction_flag ?
|
||||
sps->vui.max_dec_frame_buffering : H264_MAX_DPB_FRAMES;
|
||||
|
||||
width = 16 * (sps->pic_width_in_mbs_minus1 + 1);
|
||||
height = 16 * (sps->pic_height_in_map_units_minus1 + 1) *
|
||||
(2 - sps->frame_mbs_only_flag);
|
||||
|
||||
if (sps->vui.timing_info_present_flag)
|
||||
framerate = sps->vui.time_scale / sps->vui.num_units_in_tick / 2;
|
||||
else
|
||||
framerate = 0;
|
||||
|
||||
desc = ff_h264_guess_level(sps->profile_idc, bit_rate, framerate,
|
||||
width, height, dpb_frames);
|
||||
if (desc) {
|
||||
level_idc = desc->level_idc;
|
||||
} else {
|
||||
av_log(bsf, AV_LOG_WARNING, "Stream does not appear to "
|
||||
"conform to any level: using level 6.2.\n");
|
||||
level_idc = 62;
|
||||
}
|
||||
} else {
|
||||
level_idc = ctx->level;
|
||||
}
|
||||
|
||||
if (level_idc == 9) {
|
||||
if (sps->profile_idc == 66 ||
|
||||
sps->profile_idc == 77 ||
|
||||
sps->profile_idc == 88) {
|
||||
sps->level_idc = 11;
|
||||
sps->constraint_set3_flag = 1;
|
||||
} else {
|
||||
sps->level_idc = 9;
|
||||
}
|
||||
} else {
|
||||
sps->level_idc = level_idc;
|
||||
}
|
||||
}
|
||||
|
||||
if (need_vui)
|
||||
sps->vui_parameters_present_flag = 1;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int h264_metadata_handle_display_orientation(AVBSFContext *bsf,
|
||||
AVPacket *pkt,
|
||||
CodedBitstreamFragment *au,
|
||||
int seek_point)
|
||||
{
|
||||
H264MetadataContext *ctx = bsf->priv_data;
|
||||
SEIRawMessage *message;
|
||||
int err;
|
||||
|
||||
message = NULL;
|
||||
while (ff_cbs_sei_find_message(ctx->common.output, au,
|
||||
SEI_TYPE_DISPLAY_ORIENTATION,
|
||||
&message) == 0) {
|
||||
H264RawSEIDisplayOrientation *disp = message->payload;
|
||||
double angle = disp->anticlockwise_rotation * 180.0 / 65536.0;
|
||||
int32_t *matrix;
|
||||
|
||||
matrix = av_malloc(9 * sizeof(int32_t));
|
||||
if (!matrix)
|
||||
return AVERROR(ENOMEM);
|
||||
|
||||
/* av_display_rotation_set() expects the angle in the clockwise
|
||||
* direction, hence the first minus.
|
||||
* The below code applies the flips after the rotation, yet
|
||||
* the H.2645 specs require flipping to be applied first.
|
||||
* Because of R O(phi) = O(-phi) R (where R is flipping around
|
||||
* an arbitatry axis and O(phi) is the proper rotation by phi)
|
||||
* we can create display matrices as desired by negating
|
||||
* the degree once for every flip applied. */
|
||||
angle = -angle * (1 - 2 * !!disp->hor_flip) * (1 - 2 * !!disp->ver_flip);
|
||||
|
||||
av_display_rotation_set(matrix, angle);
|
||||
av_display_matrix_flip(matrix, disp->hor_flip, disp->ver_flip);
|
||||
|
||||
// If there are multiple display orientation messages in an
|
||||
// access unit, then the last one added to the packet (i.e.
|
||||
// the first one in the access unit) will prevail.
|
||||
err = av_packet_add_side_data(pkt, AV_PKT_DATA_DISPLAYMATRIX,
|
||||
(uint8_t*)matrix,
|
||||
9 * sizeof(int32_t));
|
||||
if (err < 0) {
|
||||
av_log(bsf, AV_LOG_ERROR, "Failed to attach extracted "
|
||||
"displaymatrix side data to packet.\n");
|
||||
av_free(matrix);
|
||||
return AVERROR(ENOMEM);
|
||||
}
|
||||
}
|
||||
|
||||
if (ctx->display_orientation == BSF_ELEMENT_REMOVE ||
|
||||
ctx->display_orientation == BSF_ELEMENT_INSERT) {
|
||||
ff_cbs_sei_delete_message_type(ctx->common.output, au,
|
||||
SEI_TYPE_DISPLAY_ORIENTATION);
|
||||
}
|
||||
|
||||
if (ctx->display_orientation == BSF_ELEMENT_INSERT) {
|
||||
H264RawSEIDisplayOrientation *disp =
|
||||
&ctx->display_orientation_payload;
|
||||
uint8_t *data;
|
||||
size_t size;
|
||||
int write = 0;
|
||||
|
||||
data = av_packet_get_side_data(pkt, AV_PKT_DATA_DISPLAYMATRIX, &size);
|
||||
if (data && size >= 9 * sizeof(int32_t)) {
|
||||
int32_t matrix[9];
|
||||
double dmatrix[9];
|
||||
int hflip, vflip, i;
|
||||
double scale_x, scale_y, angle;
|
||||
|
||||
memcpy(matrix, data, sizeof(matrix));
|
||||
|
||||
for (i = 0; i < 9; i++)
|
||||
dmatrix[i] = matrix[i] / 65536.0;
|
||||
|
||||
// Extract scale factors.
|
||||
scale_x = hypot(dmatrix[0], dmatrix[3]);
|
||||
scale_y = hypot(dmatrix[1], dmatrix[4]);
|
||||
|
||||
// Select flips to make the main diagonal positive.
|
||||
hflip = dmatrix[0] < 0.0;
|
||||
vflip = dmatrix[4] < 0.0;
|
||||
if (hflip)
|
||||
scale_x = -scale_x;
|
||||
if (vflip)
|
||||
scale_y = -scale_y;
|
||||
|
||||
// Rescale.
|
||||
for (i = 0; i < 9; i += 3) {
|
||||
dmatrix[i] /= scale_x;
|
||||
dmatrix[i + 1] /= scale_y;
|
||||
}
|
||||
|
||||
// Extract rotation.
|
||||
angle = atan2(dmatrix[3], dmatrix[0]);
|
||||
|
||||
if (!(angle >= -M_PI && angle <= M_PI) ||
|
||||
matrix[2] != 0.0 || matrix[5] != 0.0 ||
|
||||
matrix[6] != 0.0 || matrix[7] != 0.0) {
|
||||
av_log(bsf, AV_LOG_WARNING, "Input display matrix is not "
|
||||
"representable in H.264 parameters.\n");
|
||||
} else {
|
||||
disp->hor_flip = hflip;
|
||||
disp->ver_flip = vflip;
|
||||
disp->anticlockwise_rotation =
|
||||
(uint16_t)rint((angle >= 0.0 ? angle
|
||||
: angle + 2 * M_PI) *
|
||||
32768.0 / M_PI);
|
||||
write = 1;
|
||||
}
|
||||
}
|
||||
|
||||
if (seek_point) {
|
||||
if (!isnan(ctx->rotate)) {
|
||||
disp->anticlockwise_rotation =
|
||||
(uint16_t)rint((ctx->rotate >= 0.0 ? ctx->rotate
|
||||
: ctx->rotate + 360.0) *
|
||||
65536.0 / 360.0);
|
||||
write = 1;
|
||||
}
|
||||
if (ctx->flip) {
|
||||
disp->hor_flip = !!(ctx->flip & FLIP_HORIZONTAL);
|
||||
disp->ver_flip = !!(ctx->flip & FLIP_VERTICAL);
|
||||
write = 1;
|
||||
}
|
||||
}
|
||||
|
||||
if (write) {
|
||||
disp->display_orientation_repetition_period = 1;
|
||||
|
||||
err = ff_cbs_sei_add_message(ctx->common.output, au, 1,
|
||||
SEI_TYPE_DISPLAY_ORIENTATION,
|
||||
disp, NULL);
|
||||
if (err < 0) {
|
||||
av_log(bsf, AV_LOG_ERROR, "Failed to add display orientation "
|
||||
"SEI message to access unit.\n");
|
||||
return err;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int h264_metadata_update_fragment(AVBSFContext *bsf, AVPacket *pkt,
|
||||
CodedBitstreamFragment *au)
|
||||
{
|
||||
H264MetadataContext *ctx = bsf->priv_data;
|
||||
int err, i, has_sps, seek_point;
|
||||
|
||||
if (ctx->aud == BSF_ELEMENT_REMOVE) {
|
||||
for (i = au->nb_units - 1; i >= 0; i--) {
|
||||
if (au->units[i].type == H264_NAL_AUD)
|
||||
ff_cbs_delete_unit(au, i);
|
||||
}
|
||||
} else if (ctx->aud == BSF_ELEMENT_INSERT) {
|
||||
if (pkt) {
|
||||
err = h264_metadata_insert_aud(bsf, au);
|
||||
if (err < 0)
|
||||
return err;
|
||||
}
|
||||
}
|
||||
|
||||
has_sps = 0;
|
||||
for (i = 0; i < au->nb_units; i++) {
|
||||
if (au->units[i].type == H264_NAL_SPS) {
|
||||
err = h264_metadata_update_sps(bsf, au->units[i].content);
|
||||
if (err < 0)
|
||||
return err;
|
||||
has_sps = 1;
|
||||
}
|
||||
}
|
||||
|
||||
if (pkt) {
|
||||
// The current packet should be treated as a seek point for metadata
|
||||
// insertion if any of:
|
||||
// - It is the first packet in the stream.
|
||||
// - It contains an SPS, indicating that a sequence might start here.
|
||||
// - It is marked as containing a key frame.
|
||||
seek_point = !ctx->done_first_au || has_sps ||
|
||||
(pkt->flags & AV_PKT_FLAG_KEY);
|
||||
} else {
|
||||
seek_point = 0;
|
||||
}
|
||||
|
||||
if (ctx->sei_user_data && seek_point) {
|
||||
err = ff_cbs_sei_add_message(ctx->common.output, au, 1,
|
||||
SEI_TYPE_USER_DATA_UNREGISTERED,
|
||||
&ctx->sei_user_data_payload, NULL);
|
||||
if (err < 0) {
|
||||
av_log(bsf, AV_LOG_ERROR, "Failed to add user data SEI "
|
||||
"message to access unit.\n");
|
||||
return err;
|
||||
}
|
||||
}
|
||||
|
||||
if (ctx->delete_filler) {
|
||||
for (i = au->nb_units - 1; i >= 0; i--) {
|
||||
if (au->units[i].type == H264_NAL_FILLER_DATA) {
|
||||
ff_cbs_delete_unit(au, i);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
ff_cbs_sei_delete_message_type(ctx->common.output, au,
|
||||
SEI_TYPE_FILLER_PAYLOAD);
|
||||
}
|
||||
|
||||
if (pkt && ctx->display_orientation != BSF_ELEMENT_PASS) {
|
||||
err = h264_metadata_handle_display_orientation(bsf, pkt, au,
|
||||
seek_point);
|
||||
if (err < 0)
|
||||
return err;
|
||||
}
|
||||
|
||||
if (pkt)
|
||||
ctx->done_first_au = 1;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const CBSBSFType h264_metadata_type = {
|
||||
.codec_id = AV_CODEC_ID_H264,
|
||||
.fragment_name = "access unit",
|
||||
.unit_name = "NAL unit",
|
||||
.update_fragment = &h264_metadata_update_fragment,
|
||||
};
|
||||
|
||||
static int h264_metadata_init(AVBSFContext *bsf)
|
||||
{
|
||||
H264MetadataContext *ctx = bsf->priv_data;
|
||||
|
||||
if (ctx->sei_user_data) {
|
||||
SEIRawUserDataUnregistered *udu = &ctx->sei_user_data_payload;
|
||||
int i, j;
|
||||
|
||||
// Parse UUID. It must be a hex string of length 32, possibly
|
||||
// containing '-'s between hex digits (which we ignore).
|
||||
for (i = j = 0; j < 32 && i < 64 && ctx->sei_user_data[i]; i++) {
|
||||
int c, v;
|
||||
c = ctx->sei_user_data[i];
|
||||
if (c == '-') {
|
||||
continue;
|
||||
} else if (av_isxdigit(c)) {
|
||||
c = av_tolower(c);
|
||||
v = (c <= '9' ? c - '0' : c - 'a' + 10);
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
if (j & 1)
|
||||
udu->uuid_iso_iec_11578[j / 2] |= v;
|
||||
else
|
||||
udu->uuid_iso_iec_11578[j / 2] = v << 4;
|
||||
++j;
|
||||
}
|
||||
if (j == 32 && ctx->sei_user_data[i] == '+') {
|
||||
udu->data = (uint8_t*)ctx->sei_user_data + i + 1;
|
||||
udu->data_length = strlen(udu->data) + 1;
|
||||
} else {
|
||||
av_log(bsf, AV_LOG_ERROR, "Invalid user data: "
|
||||
"must be \"UUID+string\".\n");
|
||||
return AVERROR(EINVAL);
|
||||
}
|
||||
}
|
||||
|
||||
return ff_cbs_bsf_generic_init(bsf, &h264_metadata_type);
|
||||
}
|
||||
|
||||
#define OFFSET(x) offsetof(H264MetadataContext, x)
|
||||
#define FLAGS (AV_OPT_FLAG_VIDEO_PARAM|AV_OPT_FLAG_BSF_PARAM)
|
||||
static const AVOption h264_metadata_options[] = {
|
||||
BSF_ELEMENT_OPTIONS_PIR("aud", "Access Unit Delimiter NAL units",
|
||||
aud, FLAGS),
|
||||
|
||||
{ "sample_aspect_ratio", "Set sample aspect ratio (table E-1)",
|
||||
OFFSET(sample_aspect_ratio), AV_OPT_TYPE_RATIONAL,
|
||||
{ .dbl = 0.0 }, 0, 65535, FLAGS },
|
||||
|
||||
{ "overscan_appropriate_flag", "Set VUI overscan appropriate flag",
|
||||
OFFSET(overscan_appropriate_flag), AV_OPT_TYPE_INT,
|
||||
{ .i64 = -1 }, -1, 1, FLAGS },
|
||||
|
||||
{ "video_format", "Set video format (table E-2)",
|
||||
OFFSET(video_format), AV_OPT_TYPE_INT,
|
||||
{ .i64 = -1 }, -1, 7, FLAGS},
|
||||
{ "video_full_range_flag", "Set video full range flag",
|
||||
OFFSET(video_full_range_flag), AV_OPT_TYPE_INT,
|
||||
{ .i64 = -1 }, -1, 1, FLAGS },
|
||||
{ "colour_primaries", "Set colour primaries (table E-3)",
|
||||
OFFSET(colour_primaries), AV_OPT_TYPE_INT,
|
||||
{ .i64 = -1 }, -1, 255, FLAGS },
|
||||
{ "transfer_characteristics", "Set transfer characteristics (table E-4)",
|
||||
OFFSET(transfer_characteristics), AV_OPT_TYPE_INT,
|
||||
{ .i64 = -1 }, -1, 255, FLAGS },
|
||||
{ "matrix_coefficients", "Set matrix coefficients (table E-5)",
|
||||
OFFSET(matrix_coefficients), AV_OPT_TYPE_INT,
|
||||
{ .i64 = -1 }, -1, 255, FLAGS },
|
||||
|
||||
{ "chroma_sample_loc_type", "Set chroma sample location type (figure E-1)",
|
||||
OFFSET(chroma_sample_loc_type), AV_OPT_TYPE_INT,
|
||||
{ .i64 = -1 }, -1, 5, FLAGS },
|
||||
|
||||
{ "tick_rate", "Set VUI tick rate (time_scale / num_units_in_tick)",
|
||||
OFFSET(tick_rate), AV_OPT_TYPE_RATIONAL,
|
||||
{ .dbl = 0.0 }, 0, UINT_MAX, FLAGS },
|
||||
{ "fixed_frame_rate_flag", "Set VUI fixed frame rate flag",
|
||||
OFFSET(fixed_frame_rate_flag), AV_OPT_TYPE_INT,
|
||||
{ .i64 = -1 }, -1, 1, FLAGS },
|
||||
{ "zero_new_constraint_set_flags", "Set constraint_set4_flag / constraint_set5_flag to zero",
|
||||
OFFSET(zero_new_constraint_set_flags), AV_OPT_TYPE_BOOL,
|
||||
{ .i64 = 0 }, 0, 1, FLAGS },
|
||||
|
||||
{ "crop_left", "Set left border crop offset",
|
||||
OFFSET(crop_left), AV_OPT_TYPE_INT,
|
||||
{ .i64 = -1 }, -1, H264_MAX_WIDTH, FLAGS },
|
||||
{ "crop_right", "Set right border crop offset",
|
||||
OFFSET(crop_right), AV_OPT_TYPE_INT,
|
||||
{ .i64 = -1 }, -1, H264_MAX_WIDTH, FLAGS },
|
||||
{ "crop_top", "Set top border crop offset",
|
||||
OFFSET(crop_top), AV_OPT_TYPE_INT,
|
||||
{ .i64 = -1 }, -1, H264_MAX_HEIGHT, FLAGS },
|
||||
{ "crop_bottom", "Set bottom border crop offset",
|
||||
OFFSET(crop_bottom), AV_OPT_TYPE_INT,
|
||||
{ .i64 = -1 }, -1, H264_MAX_HEIGHT, FLAGS },
|
||||
|
||||
{ "sei_user_data", "Insert SEI user data (UUID+string)",
|
||||
OFFSET(sei_user_data), AV_OPT_TYPE_STRING, { .str = NULL }, .flags = FLAGS },
|
||||
|
||||
{ "delete_filler", "Delete all filler (both NAL and SEI)",
|
||||
OFFSET(delete_filler), AV_OPT_TYPE_INT, { .i64 = 0 }, 0, 1, FLAGS},
|
||||
|
||||
BSF_ELEMENT_OPTIONS_PIRE("display_orientation",
|
||||
"Display orientation SEI",
|
||||
display_orientation, FLAGS),
|
||||
|
||||
{ "rotate", "Set rotation in display orientation SEI (anticlockwise angle in degrees)",
|
||||
OFFSET(rotate), AV_OPT_TYPE_DOUBLE,
|
||||
{ .dbl = NAN }, -360.0, +360.0, FLAGS },
|
||||
{ "flip", "Set flip in display orientation SEI",
|
||||
OFFSET(flip), AV_OPT_TYPE_FLAGS,
|
||||
{ .i64 = 0 }, 0, FLIP_HORIZONTAL | FLIP_VERTICAL, FLAGS, .unit = "flip" },
|
||||
{ "horizontal", "Set hor_flip",
|
||||
0, AV_OPT_TYPE_CONST,
|
||||
{ .i64 = FLIP_HORIZONTAL }, .flags = FLAGS, .unit = "flip" },
|
||||
{ "vertical", "Set ver_flip",
|
||||
0, AV_OPT_TYPE_CONST,
|
||||
{ .i64 = FLIP_VERTICAL }, .flags = FLAGS, .unit = "flip" },
|
||||
|
||||
{ "level", "Set level (table A-1)",
|
||||
OFFSET(level), AV_OPT_TYPE_INT,
|
||||
{ .i64 = LEVEL_UNSET }, LEVEL_UNSET, 0xff, FLAGS, .unit = "level" },
|
||||
{ "auto", "Attempt to guess level from stream properties",
|
||||
0, AV_OPT_TYPE_CONST,
|
||||
{ .i64 = LEVEL_AUTO }, .flags = FLAGS, .unit = "level" },
|
||||
#define LEVEL(name, value) name, NULL, 0, AV_OPT_TYPE_CONST, \
|
||||
{ .i64 = value }, .flags = FLAGS, .unit = "level"
|
||||
{ LEVEL("1", 10) },
|
||||
{ LEVEL("1b", 9) },
|
||||
{ LEVEL("1.1", 11) },
|
||||
{ LEVEL("1.2", 12) },
|
||||
{ LEVEL("1.3", 13) },
|
||||
{ LEVEL("2", 20) },
|
||||
{ LEVEL("2.1", 21) },
|
||||
{ LEVEL("2.2", 22) },
|
||||
{ LEVEL("3", 30) },
|
||||
{ LEVEL("3.1", 31) },
|
||||
{ LEVEL("3.2", 32) },
|
||||
{ LEVEL("4", 40) },
|
||||
{ LEVEL("4.1", 41) },
|
||||
{ LEVEL("4.2", 42) },
|
||||
{ LEVEL("5", 50) },
|
||||
{ LEVEL("5.1", 51) },
|
||||
{ LEVEL("5.2", 52) },
|
||||
{ LEVEL("6", 60) },
|
||||
{ LEVEL("6.1", 61) },
|
||||
{ LEVEL("6.2", 62) },
|
||||
#undef LEVEL
|
||||
|
||||
{ NULL }
|
||||
};
|
||||
|
||||
static const AVClass h264_metadata_class = {
|
||||
.class_name = "h264_metadata_bsf",
|
||||
.item_name = av_default_item_name,
|
||||
.option = h264_metadata_options,
|
||||
.version = LIBAVUTIL_VERSION_INT,
|
||||
};
|
||||
|
||||
static const enum AVCodecID h264_metadata_codec_ids[] = {
|
||||
AV_CODEC_ID_H264, AV_CODEC_ID_NONE,
|
||||
};
|
||||
|
||||
const FFBitStreamFilter ff_h264_metadata_bsf = {
|
||||
.p.name = "h264_metadata",
|
||||
.p.codec_ids = h264_metadata_codec_ids,
|
||||
.p.priv_class = &h264_metadata_class,
|
||||
.priv_data_size = sizeof(H264MetadataContext),
|
||||
.init = &h264_metadata_init,
|
||||
.close = &ff_cbs_bsf_generic_close,
|
||||
.filter = &ff_cbs_bsf_generic_filter,
|
||||
};
|
||||
@@ -0,0 +1,473 @@
|
||||
/*
|
||||
* H.264 MP4 to Annex B byte stream format filter
|
||||
* Copyright (c) 2007 Benoit Fouet <benoit.fouet@free.fr>
|
||||
*
|
||||
* 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 <string.h>
|
||||
|
||||
#include "libavutil/avassert.h"
|
||||
#include "libavutil/intreadwrite.h"
|
||||
#include "libavutil/mem.h"
|
||||
|
||||
#include "bsf.h"
|
||||
#include "bsf_internal.h"
|
||||
#include "bytestream.h"
|
||||
#include "defs.h"
|
||||
#include "h264.h"
|
||||
#include "sei.h"
|
||||
|
||||
typedef struct H264BSFContext {
|
||||
uint8_t *sps;
|
||||
uint8_t *pps;
|
||||
int sps_size;
|
||||
int pps_size;
|
||||
unsigned sps_buf_size;
|
||||
unsigned pps_buf_size;
|
||||
uint8_t length_size;
|
||||
uint8_t new_idr;
|
||||
uint8_t idr_sps_seen;
|
||||
uint8_t idr_pps_seen;
|
||||
int extradata_parsed;
|
||||
} H264BSFContext;
|
||||
|
||||
enum PsSource {
|
||||
PS_OUT_OF_BAND = -1,
|
||||
PS_NONE = 0,
|
||||
PS_IN_BAND = 1,
|
||||
};
|
||||
|
||||
static void count_or_copy(uint8_t **out, uint64_t *out_size,
|
||||
const uint8_t *in, int in_size, enum PsSource ps, int copy)
|
||||
{
|
||||
uint8_t start_code_size;
|
||||
|
||||
if (ps == PS_OUT_OF_BAND)
|
||||
/* start code already present in out-of-band ps data, so don't need to
|
||||
* add it manually again
|
||||
*/
|
||||
start_code_size = 0;
|
||||
else if (ps == PS_IN_BAND || *out_size == 0)
|
||||
start_code_size = 4;
|
||||
else
|
||||
start_code_size = 3;
|
||||
|
||||
if (copy) {
|
||||
memcpy(*out + start_code_size, in, in_size);
|
||||
if (start_code_size == 4) {
|
||||
AV_WB32(*out, 1);
|
||||
} else if (start_code_size) {
|
||||
(*out)[0] =
|
||||
(*out)[1] = 0;
|
||||
(*out)[2] = 1;
|
||||
}
|
||||
*out += start_code_size + in_size;
|
||||
}
|
||||
*out_size += start_code_size + in_size;
|
||||
}
|
||||
|
||||
static int h264_extradata_to_annexb(AVBSFContext *ctx,
|
||||
uint8_t *extradata, int extradata_size)
|
||||
{
|
||||
H264BSFContext *s = ctx->priv_data;
|
||||
GetByteContext ogb, *gb = &ogb;
|
||||
uint16_t unit_size;
|
||||
uint32_t total_size = 0;
|
||||
uint8_t *out = NULL, unit_nb, sps_done = 0;
|
||||
static const uint8_t nalu_header[4] = { 0, 0, 0, 1 };
|
||||
const int padding = AV_INPUT_BUFFER_PADDING_SIZE;
|
||||
int length_size, pps_offset = 0;
|
||||
|
||||
if (extradata_size < 7) {
|
||||
av_log(ctx, AV_LOG_ERROR, "Invalid extradata size: %d\n", extradata_size);
|
||||
return AVERROR_INVALIDDATA;
|
||||
}
|
||||
|
||||
bytestream2_init(gb, extradata, extradata_size);
|
||||
|
||||
bytestream2_skipu(gb, 4);
|
||||
|
||||
/* retrieve length coded size */
|
||||
length_size = (bytestream2_get_byteu(gb) & 0x3) + 1;
|
||||
|
||||
/* retrieve sps and pps unit(s) */
|
||||
unit_nb = bytestream2_get_byteu(gb) & 0x1f; /* number of sps unit(s) */
|
||||
if (!unit_nb) {
|
||||
goto pps;
|
||||
}
|
||||
|
||||
while (unit_nb--) {
|
||||
int err;
|
||||
|
||||
/* possible overread ok due to padding */
|
||||
unit_size = bytestream2_get_be16u(gb);
|
||||
total_size += unit_size + 4;
|
||||
av_assert1(total_size <= INT_MAX - padding);
|
||||
if (bytestream2_get_bytes_left(gb) < unit_size + !sps_done) {
|
||||
av_log(ctx, AV_LOG_ERROR, "Global extradata truncated, "
|
||||
"corrupted stream or invalid MP4/AVCC bitstream\n");
|
||||
av_free(out);
|
||||
return AVERROR_INVALIDDATA;
|
||||
}
|
||||
if ((err = av_reallocp(&out, total_size + padding)) < 0)
|
||||
return err;
|
||||
memcpy(out + total_size - unit_size - 4, nalu_header, 4);
|
||||
bytestream2_get_bufferu(gb, out + total_size - unit_size, unit_size);
|
||||
pps:
|
||||
if (!unit_nb && !sps_done++) {
|
||||
unit_nb = bytestream2_get_byteu(gb); /* number of pps unit(s) */
|
||||
pps_offset = total_size;
|
||||
}
|
||||
}
|
||||
|
||||
if (out)
|
||||
memset(out + total_size, 0, padding);
|
||||
|
||||
if (pps_offset) {
|
||||
uint8_t *sps;
|
||||
|
||||
s->sps_size = pps_offset;
|
||||
sps = av_fast_realloc(s->sps, &s->sps_buf_size, s->sps_size);
|
||||
if (!sps) {
|
||||
av_free(out);
|
||||
return AVERROR(ENOMEM);
|
||||
}
|
||||
s->sps = sps;
|
||||
memcpy(s->sps, out, s->sps_size);
|
||||
} else {
|
||||
av_log(ctx, AV_LOG_WARNING,
|
||||
"Warning: SPS NALU missing or invalid. "
|
||||
"The resulting stream may not play.\n");
|
||||
}
|
||||
if (pps_offset < total_size) {
|
||||
uint8_t *pps;
|
||||
|
||||
s->pps_size = total_size - pps_offset;
|
||||
pps = av_fast_realloc(s->pps, &s->pps_buf_size, s->pps_size);
|
||||
if (!pps) {
|
||||
av_freep(&s->sps);
|
||||
av_free(out);
|
||||
return AVERROR(ENOMEM);
|
||||
}
|
||||
s->pps = pps;
|
||||
memcpy(s->pps, out + pps_offset, s->pps_size);
|
||||
} else {
|
||||
av_log(ctx, AV_LOG_WARNING,
|
||||
"Warning: PPS NALU missing or invalid. "
|
||||
"The resulting stream may not play.\n");
|
||||
}
|
||||
|
||||
av_freep(&ctx->par_out->extradata);
|
||||
ctx->par_out->extradata = out;
|
||||
ctx->par_out->extradata_size = total_size;
|
||||
|
||||
s->length_size = length_size;
|
||||
s->new_idr = 1;
|
||||
s->idr_sps_seen = 0;
|
||||
s->idr_pps_seen = 0;
|
||||
s->extradata_parsed = 1;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int h264_mp4toannexb_save_ps(uint8_t **dst, int *dst_size,
|
||||
unsigned *dst_buf_size,
|
||||
const uint8_t *nal, uint32_t nal_size,
|
||||
int first)
|
||||
{
|
||||
static const uint8_t nalu_header[4] = { 0, 0, 0, 1 };
|
||||
const int start_code_size = sizeof(nalu_header);
|
||||
uint8_t *ptr;
|
||||
uint32_t size;
|
||||
|
||||
if (first)
|
||||
size = 0;
|
||||
else
|
||||
size = *dst_size;
|
||||
|
||||
ptr = av_fast_realloc(*dst, dst_buf_size, size + nal_size + start_code_size);
|
||||
if (!ptr)
|
||||
return AVERROR(ENOMEM);
|
||||
|
||||
memcpy(ptr + size, nalu_header, start_code_size);
|
||||
size += start_code_size;
|
||||
memcpy(ptr + size, nal, nal_size);
|
||||
size += nal_size;
|
||||
|
||||
*dst = ptr;
|
||||
*dst_size = size;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int h264_mp4toannexb_filter_ps(H264BSFContext *s,
|
||||
const uint8_t *buf,
|
||||
const uint8_t *buf_end)
|
||||
{
|
||||
int sps_count = 0;
|
||||
int pps_count = 0;
|
||||
uint8_t unit_type;
|
||||
|
||||
do {
|
||||
uint32_t nal_size = 0;
|
||||
|
||||
/* possible overread ok due to padding */
|
||||
for (int i = 0; i < s->length_size; i++)
|
||||
nal_size = (nal_size << 8) | buf[i];
|
||||
|
||||
buf += s->length_size;
|
||||
|
||||
/* This check requires the cast as the right side might
|
||||
* otherwise be promoted to an unsigned value. */
|
||||
if ((int64_t)nal_size > buf_end - buf)
|
||||
return AVERROR_INVALIDDATA;
|
||||
|
||||
if (!nal_size)
|
||||
continue;
|
||||
|
||||
unit_type = *buf & 0x1f;
|
||||
|
||||
if (unit_type == H264_NAL_SPS) {
|
||||
h264_mp4toannexb_save_ps(&s->sps, &s->sps_size, &s->sps_buf_size, buf,
|
||||
nal_size, !sps_count);
|
||||
sps_count++;
|
||||
} else if (unit_type == H264_NAL_PPS) {
|
||||
h264_mp4toannexb_save_ps(&s->pps, &s->pps_size, &s->pps_buf_size, buf,
|
||||
nal_size, !pps_count);
|
||||
pps_count++;
|
||||
}
|
||||
|
||||
buf += nal_size;
|
||||
} while (buf < buf_end);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int h264_mp4toannexb_init(AVBSFContext *ctx)
|
||||
{
|
||||
int extra_size = ctx->par_in->extradata_size;
|
||||
|
||||
/* retrieve sps and pps NAL units from extradata */
|
||||
if (!extra_size ||
|
||||
(extra_size >= 3 && AV_RB24(ctx->par_in->extradata) == 1) ||
|
||||
(extra_size >= 4 && AV_RB32(ctx->par_in->extradata) == 1)) {
|
||||
av_log(ctx, AV_LOG_VERBOSE,
|
||||
"The input looks like it is Annex B already\n");
|
||||
return 0;
|
||||
}
|
||||
return h264_extradata_to_annexb(ctx,
|
||||
ctx->par_in->extradata,
|
||||
ctx->par_in->extradata_size);
|
||||
}
|
||||
|
||||
static int h264_mp4toannexb_filter(AVBSFContext *ctx, AVPacket *opkt)
|
||||
{
|
||||
H264BSFContext *s = ctx->priv_data;
|
||||
AVPacket *in;
|
||||
uint8_t unit_type, new_idr, sps_seen, pps_seen;
|
||||
const uint8_t *buf;
|
||||
const uint8_t *buf_end;
|
||||
uint8_t *out;
|
||||
uint64_t out_size;
|
||||
int ret;
|
||||
size_t extradata_size;
|
||||
uint8_t *extradata;
|
||||
|
||||
ret = ff_bsf_get_packet(ctx, &in);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
extradata = av_packet_get_side_data(in, AV_PKT_DATA_NEW_EXTRADATA,
|
||||
&extradata_size);
|
||||
if (extradata && extradata[0] == 1) {
|
||||
ret = h264_extradata_to_annexb(ctx, extradata, extradata_size);
|
||||
if (ret < 0)
|
||||
goto fail;
|
||||
av_packet_side_data_remove(in->side_data, &in->side_data_elems,
|
||||
AV_PKT_DATA_NEW_EXTRADATA);
|
||||
}
|
||||
|
||||
/* nothing to filter */
|
||||
if (!s->extradata_parsed) {
|
||||
av_packet_move_ref(opkt, in);
|
||||
av_packet_free(&in);
|
||||
return 0;
|
||||
}
|
||||
|
||||
buf_end = in->data + in->size;
|
||||
ret = h264_mp4toannexb_filter_ps(s, in->data, buf_end);
|
||||
if (ret < 0)
|
||||
goto fail;
|
||||
|
||||
#define LOG_ONCE(...) \
|
||||
if (j) \
|
||||
av_log(__VA_ARGS__)
|
||||
for (int j = 0; j < 2; j++) {
|
||||
buf = in->data;
|
||||
new_idr = s->new_idr;
|
||||
sps_seen = s->idr_sps_seen;
|
||||
pps_seen = s->idr_pps_seen;
|
||||
out_size = 0;
|
||||
|
||||
do {
|
||||
uint32_t nal_size = 0;
|
||||
enum PsSource ps;
|
||||
|
||||
/* possible overread ok due to padding */
|
||||
for (int i = 0; i < s->length_size; i++)
|
||||
nal_size = (nal_size << 8) | buf[i];
|
||||
|
||||
buf += s->length_size;
|
||||
|
||||
/* This check requires the cast as the right side might
|
||||
* otherwise be promoted to an unsigned value. */
|
||||
if ((int64_t)nal_size > buf_end - buf) {
|
||||
ret = AVERROR_INVALIDDATA;
|
||||
goto fail;
|
||||
}
|
||||
|
||||
if (!nal_size)
|
||||
continue;
|
||||
|
||||
unit_type = *buf & 0x1f;
|
||||
|
||||
if (unit_type == H264_NAL_SPS) {
|
||||
sps_seen = new_idr = 1;
|
||||
} else if (unit_type == H264_NAL_PPS) {
|
||||
pps_seen = new_idr = 1;
|
||||
/* if SPS has not been seen yet, prepend the AVCC one to PPS */
|
||||
if (!sps_seen) {
|
||||
if (!s->sps_size) {
|
||||
LOG_ONCE(ctx, AV_LOG_WARNING, "SPS not present in the stream, nor in AVCC, stream may be unreadable\n");
|
||||
} else {
|
||||
count_or_copy(&out, &out_size, s->sps, s->sps_size, PS_OUT_OF_BAND, j);
|
||||
sps_seen = 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* If this is a new IDR picture following an IDR picture, reset the idr flag.
|
||||
* Just check first_mb_in_slice to be 0 as this is the simplest solution.
|
||||
* This could be checking idr_pic_id instead, but would complexify the parsing. */
|
||||
if (!new_idr && unit_type == H264_NAL_IDR_SLICE && (buf[1] & 0x80))
|
||||
new_idr = 1;
|
||||
|
||||
/* If this is a buffering period SEI without a corresponding sps/pps
|
||||
* then prepend any existing sps/pps before the SEI */
|
||||
if (unit_type == H264_NAL_SEI && buf[1] == SEI_TYPE_BUFFERING_PERIOD &&
|
||||
!sps_seen && !pps_seen) {
|
||||
if (s->sps_size) {
|
||||
count_or_copy(&out, &out_size, s->sps, s->sps_size, PS_OUT_OF_BAND, j);
|
||||
sps_seen = 1;
|
||||
}
|
||||
if (s->pps_size) {
|
||||
count_or_copy(&out, &out_size, s->pps, s->pps_size, PS_OUT_OF_BAND, j);
|
||||
pps_seen = 1;
|
||||
}
|
||||
}
|
||||
|
||||
/* prepend only to the first type 5 NAL unit of an IDR picture, if no sps/pps are already present */
|
||||
if (new_idr && unit_type == H264_NAL_IDR_SLICE && !sps_seen && !pps_seen) {
|
||||
if (s->sps_size)
|
||||
count_or_copy(&out, &out_size, s->sps, s->sps_size, PS_OUT_OF_BAND, j);
|
||||
if (s->pps_size)
|
||||
count_or_copy(&out, &out_size, s->pps, s->pps_size, PS_OUT_OF_BAND, j);
|
||||
new_idr = 0;
|
||||
/* if only SPS has been seen, also insert PPS */
|
||||
} else if (new_idr && unit_type == H264_NAL_IDR_SLICE && sps_seen && !pps_seen) {
|
||||
if (!s->pps_size) {
|
||||
LOG_ONCE(ctx, AV_LOG_WARNING, "PPS not present in the stream, nor in AVCC, stream may be unreadable\n");
|
||||
} else {
|
||||
count_or_copy(&out, &out_size, s->pps, s->pps_size, PS_OUT_OF_BAND, j);
|
||||
}
|
||||
}
|
||||
|
||||
if (unit_type == H264_NAL_SPS || unit_type == H264_NAL_PPS)
|
||||
ps = PS_IN_BAND;
|
||||
else
|
||||
ps = PS_NONE;
|
||||
count_or_copy(&out, &out_size, buf, nal_size, ps, j);
|
||||
if (unit_type == H264_NAL_SLICE) {
|
||||
new_idr = 1;
|
||||
sps_seen = 0;
|
||||
pps_seen = 0;
|
||||
}
|
||||
|
||||
buf += nal_size;
|
||||
} while (buf < buf_end);
|
||||
|
||||
if (!j) {
|
||||
if (out_size > INT_MAX - AV_INPUT_BUFFER_PADDING_SIZE) {
|
||||
ret = AVERROR_INVALIDDATA;
|
||||
goto fail;
|
||||
}
|
||||
ret = av_new_packet(opkt, out_size);
|
||||
if (ret < 0)
|
||||
goto fail;
|
||||
out = opkt->data;
|
||||
}
|
||||
}
|
||||
#undef LOG_ONCE
|
||||
|
||||
av_assert1(out_size == opkt->size);
|
||||
|
||||
s->new_idr = new_idr;
|
||||
s->idr_sps_seen = sps_seen;
|
||||
s->idr_pps_seen = pps_seen;
|
||||
|
||||
ret = av_packet_copy_props(opkt, in);
|
||||
if (ret < 0)
|
||||
goto fail;
|
||||
|
||||
fail:
|
||||
if (ret < 0)
|
||||
av_packet_unref(opkt);
|
||||
av_packet_free(&in);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void h264_mp4toannexb_close(AVBSFContext *ctx)
|
||||
{
|
||||
H264BSFContext *s = ctx->priv_data;
|
||||
|
||||
av_freep(&s->sps);
|
||||
av_freep(&s->pps);
|
||||
}
|
||||
|
||||
static void h264_mp4toannexb_flush(AVBSFContext *ctx)
|
||||
{
|
||||
H264BSFContext *s = ctx->priv_data;
|
||||
|
||||
s->idr_sps_seen = 0;
|
||||
s->idr_pps_seen = 0;
|
||||
s->new_idr = s->extradata_parsed;
|
||||
}
|
||||
|
||||
static const enum AVCodecID codec_ids[] = {
|
||||
AV_CODEC_ID_H264, AV_CODEC_ID_NONE,
|
||||
};
|
||||
|
||||
const FFBitStreamFilter ff_h264_mp4toannexb_bsf = {
|
||||
.p.name = "h264_mp4toannexb",
|
||||
.p.codec_ids = codec_ids,
|
||||
.priv_data_size = sizeof(H264BSFContext),
|
||||
.init = h264_mp4toannexb_init,
|
||||
.filter = h264_mp4toannexb_filter,
|
||||
.close = h264_mp4toannexb_close,
|
||||
.flush = h264_mp4toannexb_flush,
|
||||
};
|
||||
@@ -0,0 +1,126 @@
|
||||
/*
|
||||
* 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 <inttypes.h>
|
||||
|
||||
#include "libavutil/log.h"
|
||||
|
||||
#include "bsf.h"
|
||||
#include "bsf_internal.h"
|
||||
#include "cbs.h"
|
||||
#include "cbs_bsf.h"
|
||||
#include "cbs_h264.h"
|
||||
#include "codec_id.h"
|
||||
#include "h264.h"
|
||||
#include "packet.h"
|
||||
|
||||
#define NEW_GLOBAL_PIC_INIT_QP 26
|
||||
|
||||
typedef struct H264RedundantPPSContext {
|
||||
CBSBSFContext common;
|
||||
} H264RedundantPPSContext;
|
||||
|
||||
|
||||
static int h264_redundant_pps_fixup_pps(H264RedundantPPSContext *ctx,
|
||||
CodedBitstreamUnit *unit)
|
||||
{
|
||||
H264RawPPS *pps;
|
||||
int err;
|
||||
|
||||
// The changes we are about to perform affect the parsing process,
|
||||
// so we must make sure that the PPS is writable, otherwise the
|
||||
// parsing of future slices will be incorrect and even raise errors.
|
||||
err = ff_cbs_make_unit_writable(ctx->common.input, unit);
|
||||
if (err < 0)
|
||||
return err;
|
||||
pps = unit->content;
|
||||
|
||||
// Overwrite pic_init_qp with the global value.
|
||||
pps->pic_init_qp_minus26 = NEW_GLOBAL_PIC_INIT_QP - 26;
|
||||
|
||||
// Some PPSs have this set, so it must be set in all of them.
|
||||
// (Slices which do not use such a PPS on input will still have
|
||||
// *_weight_l*flag as zero and therefore write equivalently.)
|
||||
pps->weighted_pred_flag = 1;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int h264_redundant_pps_fixup_slice(H264RedundantPPSContext *ctx,
|
||||
H264RawSliceHeader *slice)
|
||||
{
|
||||
const CodedBitstreamH264Context *const in = ctx->common.input->priv_data;
|
||||
const H264RawPPS *const pps = in->pps[slice->pic_parameter_set_id];
|
||||
|
||||
// We modified the PPS's qp value, now offset this by applying
|
||||
// the negative offset to the slices.
|
||||
slice->slice_qp_delta += pps->pic_init_qp_minus26
|
||||
- (NEW_GLOBAL_PIC_INIT_QP - 26);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int h264_redundant_pps_update_fragment(AVBSFContext *bsf,
|
||||
AVPacket *pkt,
|
||||
CodedBitstreamFragment *au)
|
||||
{
|
||||
H264RedundantPPSContext *ctx = bsf->priv_data;
|
||||
int err, i;
|
||||
|
||||
for (i = 0; i < au->nb_units; i++) {
|
||||
CodedBitstreamUnit *nal = &au->units[i];
|
||||
|
||||
if (nal->type == H264_NAL_PPS) {
|
||||
err = h264_redundant_pps_fixup_pps(ctx, nal);
|
||||
if (err < 0)
|
||||
return err;
|
||||
}
|
||||
if (nal->type == H264_NAL_SLICE ||
|
||||
nal->type == H264_NAL_IDR_SLICE) {
|
||||
H264RawSlice *slice = nal->content;
|
||||
h264_redundant_pps_fixup_slice(ctx, &slice->header);
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const CBSBSFType h264_redundant_pps_type = {
|
||||
.codec_id = AV_CODEC_ID_H264,
|
||||
.fragment_name = "access unit",
|
||||
.unit_name = "NAL unit",
|
||||
.update_fragment = &h264_redundant_pps_update_fragment,
|
||||
};
|
||||
|
||||
static int h264_redundant_pps_init(AVBSFContext *bsf)
|
||||
{
|
||||
return ff_cbs_bsf_generic_init(bsf, &h264_redundant_pps_type);
|
||||
}
|
||||
|
||||
static const enum AVCodecID h264_redundant_pps_codec_ids[] = {
|
||||
AV_CODEC_ID_H264, AV_CODEC_ID_NONE,
|
||||
};
|
||||
|
||||
const FFBitStreamFilter ff_h264_redundant_pps_bsf = {
|
||||
.p.name = "h264_redundant_pps",
|
||||
.p.codec_ids = h264_redundant_pps_codec_ids,
|
||||
.priv_data_size = sizeof(H264RedundantPPSContext),
|
||||
.init = &h264_redundant_pps_init,
|
||||
.close = &ff_cbs_bsf_generic_close,
|
||||
.filter = &ff_cbs_bsf_generic_filter,
|
||||
};
|
||||
@@ -0,0 +1,599 @@
|
||||
/*
|
||||
* 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/common.h"
|
||||
#include "libavutil/opt.h"
|
||||
|
||||
#include "bsf.h"
|
||||
#include "bsf_internal.h"
|
||||
#include "cbs.h"
|
||||
#include "cbs_bsf.h"
|
||||
#include "cbs_h265.h"
|
||||
#include "h2645data.h"
|
||||
#include "h265_profile_level.h"
|
||||
|
||||
#include "hevc/hevc.h"
|
||||
|
||||
enum {
|
||||
LEVEL_UNSET = -2,
|
||||
LEVEL_AUTO = -1,
|
||||
};
|
||||
|
||||
typedef struct H265MetadataContext {
|
||||
CBSBSFContext common;
|
||||
|
||||
H265RawAUD aud_nal;
|
||||
|
||||
int aud;
|
||||
|
||||
AVRational sample_aspect_ratio;
|
||||
|
||||
int video_format;
|
||||
int video_full_range_flag;
|
||||
int colour_primaries;
|
||||
int transfer_characteristics;
|
||||
int matrix_coefficients;
|
||||
|
||||
int chroma_sample_loc_type;
|
||||
|
||||
AVRational tick_rate;
|
||||
int poc_proportional_to_timing_flag;
|
||||
int num_ticks_poc_diff_one;
|
||||
|
||||
int crop_left;
|
||||
int crop_right;
|
||||
int crop_top;
|
||||
int crop_bottom;
|
||||
int width;
|
||||
int height;
|
||||
|
||||
int level;
|
||||
int level_guess;
|
||||
int level_warned;
|
||||
} H265MetadataContext;
|
||||
|
||||
|
||||
static void h265_metadata_guess_level(AVBSFContext *bsf,
|
||||
const CodedBitstreamFragment *au)
|
||||
{
|
||||
H265MetadataContext *ctx = bsf->priv_data;
|
||||
const H265LevelDescriptor *desc;
|
||||
const H265RawProfileTierLevel *ptl = NULL;
|
||||
const H265RawHRDParameters *hrd = NULL;
|
||||
int64_t bit_rate = 0;
|
||||
int width = 0, height = 0;
|
||||
int tile_cols = 0, tile_rows = 0;
|
||||
int max_dec_pic_buffering = 0;
|
||||
int i;
|
||||
|
||||
for (i = 0; i < au->nb_units; i++) {
|
||||
const CodedBitstreamUnit *unit = &au->units[i];
|
||||
|
||||
if (unit->type == HEVC_NAL_VPS) {
|
||||
const H265RawVPS *vps = unit->content;
|
||||
|
||||
ptl = &vps->profile_tier_level;
|
||||
max_dec_pic_buffering = vps->vps_max_dec_pic_buffering_minus1[0] + 1;
|
||||
|
||||
if (vps->vps_num_hrd_parameters > 0)
|
||||
hrd = &vps->hrd_parameters[0];
|
||||
|
||||
} else if (unit->type == HEVC_NAL_SPS) {
|
||||
const H265RawSPS *sps = unit->content;
|
||||
|
||||
ptl = &sps->profile_tier_level;
|
||||
max_dec_pic_buffering = sps->sps_max_dec_pic_buffering_minus1[0] + 1;
|
||||
|
||||
width = sps->pic_width_in_luma_samples;
|
||||
height = sps->pic_height_in_luma_samples;
|
||||
|
||||
if (sps->vui.vui_hrd_parameters_present_flag)
|
||||
hrd = &sps->vui.hrd_parameters;
|
||||
|
||||
} else if (unit->type == HEVC_NAL_PPS) {
|
||||
const H265RawPPS *pps = unit->content;
|
||||
|
||||
if (pps->tiles_enabled_flag) {
|
||||
tile_cols = pps->num_tile_columns_minus1 + 1;
|
||||
tile_rows = pps->num_tile_rows_minus1 + 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (hrd) {
|
||||
if (hrd->nal_hrd_parameters_present_flag) {
|
||||
bit_rate = (hrd->nal_sub_layer_hrd_parameters[0].bit_rate_value_minus1[0] + 1) *
|
||||
(INT64_C(1) << hrd->bit_rate_scale + 6);
|
||||
} else if (hrd->vcl_hrd_parameters_present_flag) {
|
||||
bit_rate = (hrd->vcl_sub_layer_hrd_parameters[0].bit_rate_value_minus1[0] + 1) *
|
||||
(INT64_C(1) << hrd->bit_rate_scale + 6);
|
||||
// Adjust for VCL vs. NAL limits.
|
||||
bit_rate = bit_rate * 11 / 10;
|
||||
}
|
||||
}
|
||||
|
||||
desc = ff_h265_guess_level(ptl, bit_rate, width, height,
|
||||
0, tile_rows, tile_cols,
|
||||
max_dec_pic_buffering);
|
||||
if (desc) {
|
||||
av_log(bsf, AV_LOG_DEBUG, "Stream appears to conform to "
|
||||
"level %s.\n", desc->name);
|
||||
ctx->level_guess = desc->level_idc;
|
||||
}
|
||||
}
|
||||
|
||||
static void h265_metadata_update_level(AVBSFContext *bsf,
|
||||
uint8_t *level_idc)
|
||||
{
|
||||
H265MetadataContext *ctx = bsf->priv_data;
|
||||
|
||||
if (ctx->level != LEVEL_UNSET) {
|
||||
if (ctx->level == LEVEL_AUTO) {
|
||||
if (ctx->level_guess) {
|
||||
*level_idc = ctx->level_guess;
|
||||
} else {
|
||||
if (!ctx->level_warned) {
|
||||
av_log(bsf, AV_LOG_WARNING, "Unable to determine level "
|
||||
"of stream: using level 8.5.\n");
|
||||
ctx->level_warned = 1;
|
||||
}
|
||||
*level_idc = 255;
|
||||
}
|
||||
} else {
|
||||
*level_idc = ctx->level;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static int h265_metadata_update_vps(AVBSFContext *bsf,
|
||||
H265RawVPS *vps)
|
||||
{
|
||||
H265MetadataContext *ctx = bsf->priv_data;
|
||||
|
||||
if (ctx->tick_rate.num && ctx->tick_rate.den) {
|
||||
int num, den;
|
||||
|
||||
av_reduce(&num, &den, ctx->tick_rate.num, ctx->tick_rate.den,
|
||||
UINT32_MAX > INT_MAX ? UINT32_MAX : INT_MAX);
|
||||
|
||||
vps->vps_time_scale = num;
|
||||
vps->vps_num_units_in_tick = den;
|
||||
|
||||
vps->vps_timing_info_present_flag = 1;
|
||||
|
||||
if (ctx->num_ticks_poc_diff_one > 0) {
|
||||
vps->vps_num_ticks_poc_diff_one_minus1 =
|
||||
ctx->num_ticks_poc_diff_one - 1;
|
||||
vps->vps_poc_proportional_to_timing_flag = 1;
|
||||
} else if (ctx->num_ticks_poc_diff_one == 0) {
|
||||
vps->vps_poc_proportional_to_timing_flag = 0;
|
||||
}
|
||||
}
|
||||
|
||||
h265_metadata_update_level(bsf, &vps->profile_tier_level.general_level_idc);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int h265_metadata_deduce_crop(AVBSFContext *bsf, const H265RawSPS *sps,
|
||||
int *crop_left, int *crop_right,
|
||||
int *crop_top, int *crop_bottom)
|
||||
{
|
||||
const H265MetadataContext *ctx = bsf->priv_data;
|
||||
int left = ctx->crop_left;
|
||||
int right = ctx->crop_right;
|
||||
int top = ctx->crop_top;
|
||||
int bottom = ctx->crop_bottom;
|
||||
|
||||
if (ctx->width > 0) {
|
||||
if (ctx->width > sps->pic_width_in_luma_samples) {
|
||||
av_log(bsf, AV_LOG_ERROR,
|
||||
"The width option value %d is larger than picture width %d\n",
|
||||
ctx->width, sps->pic_width_in_luma_samples);
|
||||
return AVERROR(EINVAL);
|
||||
}
|
||||
|
||||
if (left < 0) {
|
||||
if (right > 0)
|
||||
left = sps->pic_width_in_luma_samples - ctx->width - right;
|
||||
else
|
||||
left = 0;
|
||||
}
|
||||
|
||||
if (right < 0)
|
||||
right = sps->pic_width_in_luma_samples - ctx->width - left;
|
||||
|
||||
if (left < 0 || right < 0 || (left + right + ctx->width) !=
|
||||
sps->pic_width_in_luma_samples) {
|
||||
av_log(bsf, AV_LOG_ERROR,
|
||||
"Invalid value for crop_left %d, crop_right %d, width after "
|
||||
"crop %d, with picture width %d\n",
|
||||
ctx->crop_left, ctx->crop_right, ctx->width,
|
||||
sps->pic_width_in_luma_samples);
|
||||
return AVERROR(EINVAL);
|
||||
}
|
||||
}
|
||||
|
||||
if (ctx->height > 0) {
|
||||
if (ctx->height > sps->pic_height_in_luma_samples) {
|
||||
av_log(bsf, AV_LOG_ERROR,
|
||||
"The height option value %d is larger than picture height %d\n",
|
||||
ctx->height, sps->pic_height_in_luma_samples);
|
||||
return AVERROR(EINVAL);
|
||||
}
|
||||
|
||||
if (top < 0) {
|
||||
if (bottom > 0)
|
||||
top = sps->pic_height_in_luma_samples - ctx->height - bottom;
|
||||
else
|
||||
top = 0;
|
||||
}
|
||||
|
||||
if (bottom < 0)
|
||||
bottom = sps->pic_height_in_luma_samples - ctx->height - top;
|
||||
|
||||
if (top < 0 || bottom < 0 || (top + bottom + ctx->height) !=
|
||||
sps->pic_height_in_luma_samples) {
|
||||
av_log(bsf, AV_LOG_ERROR,
|
||||
"Invalid value for crop_top %d, crop_bottom %d, height after "
|
||||
"crop %d, with picture height %d\n",
|
||||
ctx->crop_top, ctx->crop_bottom, ctx->height,
|
||||
sps->pic_height_in_luma_samples);
|
||||
return AVERROR(EINVAL);
|
||||
}
|
||||
}
|
||||
|
||||
*crop_left = left;
|
||||
*crop_right = right;
|
||||
*crop_top = top;
|
||||
*crop_bottom = bottom;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int h265_metadata_update_sps(AVBSFContext *bsf,
|
||||
H265RawSPS *sps)
|
||||
{
|
||||
H265MetadataContext *ctx = bsf->priv_data;
|
||||
int need_vui = 0;
|
||||
int crop_unit_x, crop_unit_y;
|
||||
/* Use local variables to avoid modifying context fields in case of video
|
||||
* resolution changed. Crop doesn't work well with resolution change, this
|
||||
* is the best we can do.
|
||||
*/
|
||||
int crop_left, crop_right, crop_top, crop_bottom;
|
||||
int ret;
|
||||
|
||||
if (ctx->sample_aspect_ratio.num && ctx->sample_aspect_ratio.den) {
|
||||
int num, den, i;
|
||||
|
||||
av_reduce(&num, &den, ctx->sample_aspect_ratio.num,
|
||||
ctx->sample_aspect_ratio.den, 65535);
|
||||
|
||||
for (i = 1; i < FF_ARRAY_ELEMS(ff_h2645_pixel_aspect); i++) {
|
||||
if (num == ff_h2645_pixel_aspect[i].num &&
|
||||
den == ff_h2645_pixel_aspect[i].den)
|
||||
break;
|
||||
}
|
||||
if (i == FF_ARRAY_ELEMS(ff_h2645_pixel_aspect)) {
|
||||
sps->vui.aspect_ratio_idc = 255;
|
||||
sps->vui.sar_width = num;
|
||||
sps->vui.sar_height = den;
|
||||
} else {
|
||||
sps->vui.aspect_ratio_idc = i;
|
||||
}
|
||||
sps->vui.aspect_ratio_info_present_flag = 1;
|
||||
need_vui = 1;
|
||||
}
|
||||
|
||||
#define SET_OR_INFER(field, value, present_flag, infer) do { \
|
||||
if (value >= 0) { \
|
||||
field = value; \
|
||||
need_vui = 1; \
|
||||
} else if (!present_flag) \
|
||||
field = infer; \
|
||||
} while (0)
|
||||
|
||||
if (ctx->video_format >= 0 ||
|
||||
ctx->video_full_range_flag >= 0 ||
|
||||
ctx->colour_primaries >= 0 ||
|
||||
ctx->transfer_characteristics >= 0 ||
|
||||
ctx->matrix_coefficients >= 0) {
|
||||
|
||||
SET_OR_INFER(sps->vui.video_format, ctx->video_format,
|
||||
sps->vui.video_signal_type_present_flag, 5);
|
||||
|
||||
SET_OR_INFER(sps->vui.video_full_range_flag,
|
||||
ctx->video_full_range_flag,
|
||||
sps->vui.video_signal_type_present_flag, 0);
|
||||
|
||||
if (ctx->colour_primaries >= 0 ||
|
||||
ctx->transfer_characteristics >= 0 ||
|
||||
ctx->matrix_coefficients >= 0) {
|
||||
|
||||
SET_OR_INFER(sps->vui.colour_primaries,
|
||||
ctx->colour_primaries,
|
||||
sps->vui.colour_description_present_flag, 2);
|
||||
|
||||
SET_OR_INFER(sps->vui.transfer_characteristics,
|
||||
ctx->transfer_characteristics,
|
||||
sps->vui.colour_description_present_flag, 2);
|
||||
|
||||
SET_OR_INFER(sps->vui.matrix_coefficients,
|
||||
ctx->matrix_coefficients,
|
||||
sps->vui.colour_description_present_flag, 2);
|
||||
|
||||
sps->vui.colour_description_present_flag = 1;
|
||||
}
|
||||
sps->vui.video_signal_type_present_flag = 1;
|
||||
need_vui = 1;
|
||||
}
|
||||
|
||||
if (ctx->chroma_sample_loc_type >= 0) {
|
||||
sps->vui.chroma_sample_loc_type_top_field =
|
||||
ctx->chroma_sample_loc_type;
|
||||
sps->vui.chroma_sample_loc_type_bottom_field =
|
||||
ctx->chroma_sample_loc_type;
|
||||
sps->vui.chroma_loc_info_present_flag = 1;
|
||||
need_vui = 1;
|
||||
}
|
||||
|
||||
if (ctx->tick_rate.num && ctx->tick_rate.den) {
|
||||
int num, den;
|
||||
|
||||
av_reduce(&num, &den, ctx->tick_rate.num, ctx->tick_rate.den,
|
||||
UINT32_MAX > INT_MAX ? UINT32_MAX : INT_MAX);
|
||||
|
||||
sps->vui.vui_time_scale = num;
|
||||
sps->vui.vui_num_units_in_tick = den;
|
||||
|
||||
sps->vui.vui_timing_info_present_flag = 1;
|
||||
need_vui = 1;
|
||||
|
||||
if (ctx->num_ticks_poc_diff_one > 0) {
|
||||
sps->vui.vui_num_ticks_poc_diff_one_minus1 =
|
||||
ctx->num_ticks_poc_diff_one - 1;
|
||||
sps->vui.vui_poc_proportional_to_timing_flag = 1;
|
||||
} else if (ctx->num_ticks_poc_diff_one == 0) {
|
||||
sps->vui.vui_poc_proportional_to_timing_flag = 0;
|
||||
}
|
||||
}
|
||||
|
||||
ret = h265_metadata_deduce_crop(bsf, sps, &crop_left, &crop_right,
|
||||
&crop_top, &crop_bottom);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
if (sps->separate_colour_plane_flag || sps->chroma_format_idc == 0) {
|
||||
crop_unit_x = 1;
|
||||
crop_unit_y = 1;
|
||||
} else {
|
||||
crop_unit_x = 1 + (sps->chroma_format_idc < 3);
|
||||
crop_unit_y = 1 + (sps->chroma_format_idc < 2);
|
||||
}
|
||||
#define CROP(border, unit) do { \
|
||||
if (crop_ ## border >= 0) { \
|
||||
if (crop_ ## border % unit != 0) { \
|
||||
av_log(bsf, AV_LOG_ERROR, "Invalid value for crop_%s: " \
|
||||
"must be a multiple of %d.\n", #border, unit); \
|
||||
return AVERROR(EINVAL); \
|
||||
} \
|
||||
sps->conf_win_ ## border ## _offset = \
|
||||
crop_ ## border / unit; \
|
||||
sps->conformance_window_flag = 1; \
|
||||
} \
|
||||
} while (0)
|
||||
CROP(left, crop_unit_x);
|
||||
CROP(right, crop_unit_x);
|
||||
CROP(top, crop_unit_y);
|
||||
CROP(bottom, crop_unit_y);
|
||||
#undef CROP
|
||||
|
||||
if (need_vui)
|
||||
sps->vui_parameters_present_flag = 1;
|
||||
|
||||
h265_metadata_update_level(bsf, &sps->profile_tier_level.general_level_idc);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int h265_metadata_update_fragment(AVBSFContext *bsf, AVPacket *pkt,
|
||||
CodedBitstreamFragment *au)
|
||||
{
|
||||
H265MetadataContext *ctx = bsf->priv_data;
|
||||
int err, i;
|
||||
|
||||
// If an AUD is present, it must be the first NAL unit.
|
||||
if (au->nb_units && au->units[0].type == HEVC_NAL_AUD) {
|
||||
if (ctx->aud == BSF_ELEMENT_REMOVE)
|
||||
ff_cbs_delete_unit(au, 0);
|
||||
} else {
|
||||
if (pkt && ctx->aud == BSF_ELEMENT_INSERT) {
|
||||
H265RawAUD *aud = &ctx->aud_nal;
|
||||
int pic_type = 0, temporal_id = 8, layer_id = 0;
|
||||
|
||||
for (i = 0; i < au->nb_units; i++) {
|
||||
const H265RawNALUnitHeader *nal = au->units[i].content;
|
||||
if (!nal)
|
||||
continue;
|
||||
if (nal->nuh_temporal_id_plus1 < temporal_id + 1)
|
||||
temporal_id = nal->nuh_temporal_id_plus1 - 1;
|
||||
|
||||
if (au->units[i].type <= HEVC_NAL_RSV_VCL31) {
|
||||
const H265RawSlice *slice = au->units[i].content;
|
||||
layer_id = nal->nuh_layer_id;
|
||||
if (slice->header.slice_type == HEVC_SLICE_B &&
|
||||
pic_type < 2)
|
||||
pic_type = 2;
|
||||
if (slice->header.slice_type == HEVC_SLICE_P &&
|
||||
pic_type < 1)
|
||||
pic_type = 1;
|
||||
}
|
||||
}
|
||||
|
||||
aud->nal_unit_header = (H265RawNALUnitHeader) {
|
||||
.nal_unit_type = HEVC_NAL_AUD,
|
||||
.nuh_layer_id = layer_id,
|
||||
.nuh_temporal_id_plus1 = temporal_id + 1,
|
||||
};
|
||||
aud->pic_type = pic_type;
|
||||
|
||||
err = ff_cbs_insert_unit_content(au, 0, HEVC_NAL_AUD, aud, NULL);
|
||||
if (err < 0) {
|
||||
av_log(bsf, AV_LOG_ERROR, "Failed to insert AUD.\n");
|
||||
return err;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (ctx->level == LEVEL_AUTO && !ctx->level_guess)
|
||||
h265_metadata_guess_level(bsf, au);
|
||||
|
||||
for (i = 0; i < au->nb_units; i++) {
|
||||
if (au->units[i].type == HEVC_NAL_VPS) {
|
||||
err = h265_metadata_update_vps(bsf, au->units[i].content);
|
||||
if (err < 0)
|
||||
return err;
|
||||
}
|
||||
if (au->units[i].type == HEVC_NAL_SPS) {
|
||||
err = h265_metadata_update_sps(bsf, au->units[i].content);
|
||||
if (err < 0)
|
||||
return err;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const CBSBSFType h265_metadata_type = {
|
||||
.codec_id = AV_CODEC_ID_HEVC,
|
||||
.fragment_name = "access unit",
|
||||
.unit_name = "NAL unit",
|
||||
.update_fragment = &h265_metadata_update_fragment,
|
||||
};
|
||||
|
||||
static int h265_metadata_init(AVBSFContext *bsf)
|
||||
{
|
||||
return ff_cbs_bsf_generic_init(bsf, &h265_metadata_type);
|
||||
}
|
||||
|
||||
#define OFFSET(x) offsetof(H265MetadataContext, x)
|
||||
#define FLAGS (AV_OPT_FLAG_VIDEO_PARAM|AV_OPT_FLAG_BSF_PARAM)
|
||||
static const AVOption h265_metadata_options[] = {
|
||||
BSF_ELEMENT_OPTIONS_PIR("aud", "Access Unit Delimiter NAL units",
|
||||
aud, FLAGS),
|
||||
|
||||
{ "sample_aspect_ratio", "Set sample aspect ratio (table E-1)",
|
||||
OFFSET(sample_aspect_ratio), AV_OPT_TYPE_RATIONAL,
|
||||
{ .dbl = 0.0 }, 0, 65535, FLAGS },
|
||||
|
||||
{ "video_format", "Set video format (table E-2)",
|
||||
OFFSET(video_format), AV_OPT_TYPE_INT,
|
||||
{ .i64 = -1 }, -1, 7, FLAGS },
|
||||
{ "video_full_range_flag", "Set video full range flag",
|
||||
OFFSET(video_full_range_flag), AV_OPT_TYPE_INT,
|
||||
{ .i64 = -1 }, -1, 1, FLAGS },
|
||||
{ "colour_primaries", "Set colour primaries (table E-3)",
|
||||
OFFSET(colour_primaries), AV_OPT_TYPE_INT,
|
||||
{ .i64 = -1 }, -1, 255, FLAGS },
|
||||
{ "transfer_characteristics", "Set transfer characteristics (table E-4)",
|
||||
OFFSET(transfer_characteristics), AV_OPT_TYPE_INT,
|
||||
{ .i64 = -1 }, -1, 255, FLAGS },
|
||||
{ "matrix_coefficients", "Set matrix coefficients (table E-5)",
|
||||
OFFSET(matrix_coefficients), AV_OPT_TYPE_INT,
|
||||
{ .i64 = -1 }, -1, 255, FLAGS },
|
||||
|
||||
{ "chroma_sample_loc_type", "Set chroma sample location type (figure E-1)",
|
||||
OFFSET(chroma_sample_loc_type), AV_OPT_TYPE_INT,
|
||||
{ .i64 = -1 }, -1, 5, FLAGS },
|
||||
|
||||
{ "tick_rate",
|
||||
"Set VPS and VUI tick rate (time_scale / num_units_in_tick)",
|
||||
OFFSET(tick_rate), AV_OPT_TYPE_RATIONAL,
|
||||
{ .dbl = 0.0 }, 0, UINT_MAX, FLAGS },
|
||||
{ "num_ticks_poc_diff_one",
|
||||
"Set VPS and VUI number of ticks per POC increment",
|
||||
OFFSET(num_ticks_poc_diff_one), AV_OPT_TYPE_INT,
|
||||
{ .i64 = -1 }, -1, INT_MAX, FLAGS },
|
||||
|
||||
{ "crop_left", "Set left border crop offset",
|
||||
OFFSET(crop_left), AV_OPT_TYPE_INT,
|
||||
{ .i64 = -1 }, -1, HEVC_MAX_WIDTH, FLAGS },
|
||||
{ "crop_right", "Set right border crop offset",
|
||||
OFFSET(crop_right), AV_OPT_TYPE_INT,
|
||||
{ .i64 = -1 }, -1, HEVC_MAX_WIDTH, FLAGS },
|
||||
{ "crop_top", "Set top border crop offset",
|
||||
OFFSET(crop_top), AV_OPT_TYPE_INT,
|
||||
{ .i64 = -1 }, -1, HEVC_MAX_HEIGHT, FLAGS },
|
||||
{ "crop_bottom", "Set bottom border crop offset",
|
||||
OFFSET(crop_bottom), AV_OPT_TYPE_INT,
|
||||
{ .i64 = -1 }, -1, HEVC_MAX_HEIGHT, FLAGS },
|
||||
{ "width", "Set width after crop",
|
||||
OFFSET(width), AV_OPT_TYPE_INT,
|
||||
{ .i64 = -1 }, -1, HEVC_MAX_WIDTH, FLAGS },
|
||||
{ "height", "Set height after crop",
|
||||
OFFSET(height), AV_OPT_TYPE_INT,
|
||||
{ .i64 = -1 }, -1, HEVC_MAX_HEIGHT, FLAGS },
|
||||
|
||||
{ "level", "Set level (tables A.6 and A.7)",
|
||||
OFFSET(level), AV_OPT_TYPE_INT,
|
||||
{ .i64 = LEVEL_UNSET }, LEVEL_UNSET, 0xff, FLAGS, .unit = "level" },
|
||||
{ "auto", "Attempt to guess level from stream properties",
|
||||
0, AV_OPT_TYPE_CONST,
|
||||
{ .i64 = LEVEL_AUTO }, .flags = FLAGS, .unit = "level" },
|
||||
#define LEVEL(name, value) name, NULL, 0, AV_OPT_TYPE_CONST, \
|
||||
{ .i64 = value }, .flags = FLAGS, .unit = "level"
|
||||
{ LEVEL("1", 30) },
|
||||
{ LEVEL("2", 60) },
|
||||
{ LEVEL("2.1", 63) },
|
||||
{ LEVEL("3", 90) },
|
||||
{ LEVEL("3.1", 93) },
|
||||
{ LEVEL("4", 120) },
|
||||
{ LEVEL("4.1", 123) },
|
||||
{ LEVEL("5", 150) },
|
||||
{ LEVEL("5.1", 153) },
|
||||
{ LEVEL("5.2", 156) },
|
||||
{ LEVEL("6", 180) },
|
||||
{ LEVEL("6.1", 183) },
|
||||
{ LEVEL("6.2", 186) },
|
||||
{ LEVEL("8.5", 255) },
|
||||
#undef LEVEL
|
||||
|
||||
{ NULL }
|
||||
};
|
||||
|
||||
static const AVClass h265_metadata_class = {
|
||||
.class_name = "h265_metadata_bsf",
|
||||
.item_name = av_default_item_name,
|
||||
.option = h265_metadata_options,
|
||||
.version = LIBAVUTIL_VERSION_INT,
|
||||
};
|
||||
|
||||
static const enum AVCodecID h265_metadata_codec_ids[] = {
|
||||
AV_CODEC_ID_HEVC, AV_CODEC_ID_NONE,
|
||||
};
|
||||
|
||||
const FFBitStreamFilter ff_hevc_metadata_bsf = {
|
||||
.p.name = "hevc_metadata",
|
||||
.p.codec_ids = h265_metadata_codec_ids,
|
||||
.p.priv_class = &h265_metadata_class,
|
||||
.priv_data_size = sizeof(H265MetadataContext),
|
||||
.init = &h265_metadata_init,
|
||||
.close = &ff_cbs_bsf_generic_close,
|
||||
.filter = &ff_cbs_bsf_generic_filter,
|
||||
};
|
||||
@@ -0,0 +1,150 @@
|
||||
/*
|
||||
* 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/common.h"
|
||||
#include "libavutil/opt.h"
|
||||
|
||||
#include "bsf.h"
|
||||
#include "bsf_internal.h"
|
||||
#include "cbs.h"
|
||||
#include "cbs_bsf.h"
|
||||
#include "cbs_h266.h"
|
||||
#include "vvc.h"
|
||||
|
||||
#define IS_H266_SLICE(nut) (nut <= VVC_RASL_NUT || (nut >= VVC_IDR_W_RADL && nut <= VVC_GDR_NUT))
|
||||
|
||||
typedef struct H266MetadataContext {
|
||||
CBSBSFContext common;
|
||||
|
||||
H266RawAUD aud_nal;
|
||||
|
||||
int aud;
|
||||
} H266MetadataContext;
|
||||
|
||||
static int h266_metadata_update_fragment(AVBSFContext *bsf, AVPacket *pkt,
|
||||
CodedBitstreamFragment *pu)
|
||||
{
|
||||
H266MetadataContext *ctx = bsf->priv_data;
|
||||
int err, i;
|
||||
|
||||
// If an AUD is present, it must be the first NAL unit.
|
||||
if (pu->nb_units && pu->units[0].type == VVC_AUD_NUT) {
|
||||
if (ctx->aud == BSF_ELEMENT_REMOVE)
|
||||
ff_cbs_delete_unit(pu, 0);
|
||||
} else if ( pkt && ctx->aud == BSF_ELEMENT_INSERT) {
|
||||
const H266RawSlice *first_slice = NULL;
|
||||
const H266RawPictureHeader *ph = NULL;
|
||||
H266RawAUD *aud = &ctx->aud_nal;
|
||||
int pic_type = 0, temporal_id = 8, layer_id = 0;
|
||||
for (i = 0; i < pu->nb_units; i++) {
|
||||
const H266RawNALUnitHeader *nal = pu->units[i].content;
|
||||
if (!nal)
|
||||
continue;
|
||||
if (nal->nuh_temporal_id_plus1 < temporal_id + 1)
|
||||
temporal_id = nal->nuh_temporal_id_plus1 - 1;
|
||||
if ( nal->nal_unit_type == VVC_PH_NUT ) {
|
||||
const H266RawPH *header = pu->units[i].content;
|
||||
ph = &header->ph_picture_header;
|
||||
} else if (IS_H266_SLICE(nal->nal_unit_type)) {
|
||||
const H266RawSlice *slice = pu->units[i].content;
|
||||
layer_id = nal->nuh_layer_id;
|
||||
if (slice->header.sh_slice_type == VVC_SLICE_TYPE_B &&
|
||||
pic_type < 2)
|
||||
pic_type = 2;
|
||||
if (slice->header.sh_slice_type == VVC_SLICE_TYPE_P &&
|
||||
pic_type < 1)
|
||||
pic_type = 1;
|
||||
if (!first_slice) {
|
||||
first_slice = slice;
|
||||
if (first_slice->header.
|
||||
sh_picture_header_in_slice_header_flag)
|
||||
ph = &first_slice->header.sh_picture_header;
|
||||
else if (!ph)
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!ph) {
|
||||
av_log(bsf, AV_LOG_ERROR, "no available picture header");
|
||||
return AVERROR_INVALIDDATA;
|
||||
}
|
||||
|
||||
aud->nal_unit_header = (H266RawNALUnitHeader) {
|
||||
.nal_unit_type = VVC_AUD_NUT,
|
||||
.nuh_layer_id = layer_id,
|
||||
.nuh_temporal_id_plus1 = temporal_id + 1,
|
||||
};
|
||||
aud->aud_pic_type = pic_type;
|
||||
aud->aud_irap_or_gdr_flag = ph->ph_gdr_or_irap_pic_flag;
|
||||
|
||||
err = ff_cbs_insert_unit_content(pu, 0, VVC_AUD_NUT, aud, NULL);
|
||||
if (err < 0) {
|
||||
av_log(bsf, AV_LOG_ERROR, "Failed to insert AUD.\n");
|
||||
return err;
|
||||
}
|
||||
}
|
||||
|
||||
/* TODO: implement more metadata parsing, like VUI, Levels etc. */
|
||||
//for (i = 0; i < pu->nb_units; i++) {
|
||||
// if (pu->units[i].type == VVC_SPS_NUT) {
|
||||
// }
|
||||
//}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const CBSBSFType h266_metadata_type = {
|
||||
.codec_id = AV_CODEC_ID_VVC,
|
||||
.fragment_name = "access unit",
|
||||
.unit_name = "NAL unit",
|
||||
.update_fragment = &h266_metadata_update_fragment,
|
||||
};
|
||||
|
||||
static int h266_metadata_init(AVBSFContext *bsf)
|
||||
{
|
||||
return ff_cbs_bsf_generic_init(bsf, &h266_metadata_type);
|
||||
}
|
||||
|
||||
#define OFFSET(x) offsetof(H266MetadataContext, x)
|
||||
#define FLAGS (AV_OPT_FLAG_VIDEO_PARAM|AV_OPT_FLAG_BSF_PARAM)
|
||||
static const AVOption h266_metadata_options[] = {
|
||||
BSF_ELEMENT_OPTIONS_PIR("aud", "Access Unit Delimiter NAL units",
|
||||
aud, FLAGS),
|
||||
|
||||
{ NULL }
|
||||
};
|
||||
|
||||
static const AVClass h266_metadata_class = {
|
||||
.class_name = "h266_metadata_bsf",
|
||||
.item_name = av_default_item_name,
|
||||
.option = h266_metadata_options,
|
||||
.version = LIBAVUTIL_VERSION_INT,
|
||||
};
|
||||
|
||||
static const enum AVCodecID h266_metadata_codec_ids[] = {
|
||||
AV_CODEC_ID_VVC, AV_CODEC_ID_NONE,
|
||||
};
|
||||
|
||||
const FFBitStreamFilter ff_vvc_metadata_bsf = {
|
||||
.p.name = "vvc_metadata",
|
||||
.p.codec_ids = h266_metadata_codec_ids,
|
||||
.p.priv_class = &h266_metadata_class,
|
||||
.priv_data_size = sizeof(H266MetadataContext),
|
||||
.init = &h266_metadata_init,
|
||||
.close = &ff_cbs_bsf_generic_close,
|
||||
.filter = &ff_cbs_bsf_generic_filter,
|
||||
};
|
||||
@@ -0,0 +1,136 @@
|
||||
/*
|
||||
* HAPQA extract bitstream filter
|
||||
* Copyright (c) 2017 Jokyo Images
|
||||
*
|
||||
* 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
|
||||
*/
|
||||
|
||||
/**
|
||||
* @file
|
||||
* HAPQA extract bitstream filter
|
||||
* extract one of the two textures of the HAQA
|
||||
*/
|
||||
|
||||
#include "bsf.h"
|
||||
#include "bsf_internal.h"
|
||||
#include "bytestream.h"
|
||||
#include "hap.h"
|
||||
|
||||
#include "libavutil/opt.h"
|
||||
|
||||
typedef struct HapqaExtractContext {
|
||||
const AVClass *class;
|
||||
int texture;/* index of the texture to keep (0 for rgb or 1 for alpha) */
|
||||
} HapqaExtractContext;
|
||||
|
||||
static int check_texture(HapqaExtractContext *ctx, int section_type) {
|
||||
if (((ctx->texture == 0)&&((section_type & 0x0F) == 0x0F)) || /* HapQ texture and rgb extract */
|
||||
((ctx->texture == 1)&&((section_type & 0x0F) == 0x01))) /* HapAlphaOnly texture and alpha extract */
|
||||
{
|
||||
return 1; /* the texture is the one to keep */
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
static int hapqa_extract(AVBSFContext *bsf, AVPacket *pkt)
|
||||
{
|
||||
HapqaExtractContext *ctx = bsf->priv_data;
|
||||
GetByteContext gbc;
|
||||
int section_size;
|
||||
enum HapSectionType section_type;
|
||||
int start_section_size;
|
||||
int target_packet_size = 0;
|
||||
int ret = 0;
|
||||
|
||||
ret = ff_bsf_get_packet_ref(bsf, pkt);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
bytestream2_init(&gbc, pkt->data, pkt->size);
|
||||
ret = ff_hap_parse_section_header(&gbc, §ion_size, §ion_type);
|
||||
if (ret != 0)
|
||||
goto fail;
|
||||
|
||||
if ((section_type & 0x0F) != 0x0D) {
|
||||
av_log(bsf, AV_LOG_ERROR, "Invalid section type for HAPQA %#04x.\n", section_type & 0x0F);
|
||||
ret = AVERROR_INVALIDDATA;
|
||||
goto fail;
|
||||
}
|
||||
|
||||
start_section_size = 4;
|
||||
|
||||
bytestream2_seek(&gbc, start_section_size, SEEK_SET);/* go to start of the first texture */
|
||||
|
||||
ret = ff_hap_parse_section_header(&gbc, §ion_size, §ion_type);
|
||||
if (ret != 0)
|
||||
goto fail;
|
||||
|
||||
target_packet_size = section_size + 4;
|
||||
|
||||
if (check_texture(ctx, section_type) == 0) { /* the texture is not the one to keep */
|
||||
start_section_size += 4 + section_size;
|
||||
bytestream2_seek(&gbc, start_section_size, SEEK_SET);/* go to start of the second texture */
|
||||
ret = ff_hap_parse_section_header(&gbc, §ion_size, §ion_type);
|
||||
if (ret != 0)
|
||||
goto fail;
|
||||
|
||||
target_packet_size = section_size + 4;
|
||||
|
||||
if (check_texture(ctx, section_type) == 0){ /* the second texture is not the one to keep */
|
||||
av_log(bsf, AV_LOG_ERROR, "No valid texture found.\n");
|
||||
ret = AVERROR_INVALIDDATA;
|
||||
goto fail;
|
||||
}
|
||||
}
|
||||
|
||||
pkt->data += start_section_size;
|
||||
pkt->size = target_packet_size;
|
||||
|
||||
fail:
|
||||
if (ret < 0)
|
||||
av_packet_unref(pkt);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static const enum AVCodecID codec_ids[] = {
|
||||
AV_CODEC_ID_HAP, AV_CODEC_ID_NONE,
|
||||
};
|
||||
|
||||
#define OFFSET(x) offsetof(HapqaExtractContext, x)
|
||||
#define FLAGS (AV_OPT_FLAG_VIDEO_PARAM | AV_OPT_FLAG_BSF_PARAM)
|
||||
static const AVOption options[] = {
|
||||
{ "texture", "texture to keep", OFFSET(texture), AV_OPT_TYPE_INT, { .i64 = 0 }, 0, 1, FLAGS, .unit = "texture" },
|
||||
{ "color", "keep HapQ texture", 0, AV_OPT_TYPE_CONST, { .i64 = 0 }, 0, 0, FLAGS, .unit = "texture" },
|
||||
{ "alpha", "keep HapAlphaOnly texture", 0, AV_OPT_TYPE_CONST, { .i64 = 1 }, 0, 0, FLAGS, .unit = "texture" },
|
||||
{ NULL },
|
||||
};
|
||||
|
||||
static const AVClass hapqa_extract_class = {
|
||||
.class_name = "hapqa_extract_bsf",
|
||||
.item_name = av_default_item_name,
|
||||
.option = options,
|
||||
.version = LIBAVUTIL_VERSION_INT,
|
||||
};
|
||||
|
||||
const FFBitStreamFilter ff_hapqa_extract_bsf = {
|
||||
.p.name = "hapqa_extract",
|
||||
.p.codec_ids = codec_ids,
|
||||
.p.priv_class = &hapqa_extract_class,
|
||||
.priv_data_size = sizeof(HapqaExtractContext),
|
||||
.filter = hapqa_extract,
|
||||
};
|
||||
@@ -0,0 +1,238 @@
|
||||
/*
|
||||
* HEVC MP4 to Annex B byte stream format filter
|
||||
* copyright (c) 2015 Anton Khirnov
|
||||
*
|
||||
* 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 <string.h>
|
||||
|
||||
#include "libavutil/intreadwrite.h"
|
||||
#include "libavutil/mem.h"
|
||||
|
||||
#include "bsf.h"
|
||||
#include "bsf_internal.h"
|
||||
#include "bytestream.h"
|
||||
#include "defs.h"
|
||||
|
||||
#include "hevc/hevc.h"
|
||||
|
||||
#define MIN_HEVCC_LENGTH 23
|
||||
|
||||
typedef struct HEVCBSFContext {
|
||||
uint8_t length_size;
|
||||
int extradata_parsed;
|
||||
} HEVCBSFContext;
|
||||
|
||||
static int hevc_extradata_to_annexb(AVBSFContext *ctx)
|
||||
{
|
||||
GetByteContext gb;
|
||||
int length_size, num_arrays, i, j;
|
||||
int ret = 0;
|
||||
|
||||
uint8_t *new_extradata = NULL;
|
||||
size_t new_extradata_size = 0;
|
||||
|
||||
bytestream2_init(&gb, ctx->par_in->extradata, ctx->par_in->extradata_size);
|
||||
|
||||
bytestream2_skip(&gb, 21);
|
||||
length_size = (bytestream2_get_byte(&gb) & 3) + 1;
|
||||
num_arrays = bytestream2_get_byte(&gb);
|
||||
|
||||
for (i = 0; i < num_arrays; i++) {
|
||||
int type = bytestream2_get_byte(&gb) & 0x3f;
|
||||
int cnt = bytestream2_get_be16(&gb);
|
||||
|
||||
if (!(type == HEVC_NAL_VPS || type == HEVC_NAL_SPS || type == HEVC_NAL_PPS ||
|
||||
type == HEVC_NAL_SEI_PREFIX || type == HEVC_NAL_SEI_SUFFIX)) {
|
||||
av_log(ctx, AV_LOG_ERROR, "Invalid NAL unit type in extradata: %d\n",
|
||||
type);
|
||||
ret = AVERROR_INVALIDDATA;
|
||||
goto fail;
|
||||
}
|
||||
|
||||
for (j = 0; j < cnt; j++) {
|
||||
const int nalu_len = bytestream2_get_be16(&gb);
|
||||
|
||||
if (!nalu_len ||
|
||||
nalu_len > bytestream2_get_bytes_left(&gb) ||
|
||||
4 + AV_INPUT_BUFFER_PADDING_SIZE + nalu_len > SIZE_MAX - new_extradata_size) {
|
||||
ret = AVERROR_INVALIDDATA;
|
||||
goto fail;
|
||||
}
|
||||
ret = av_reallocp(&new_extradata, new_extradata_size + nalu_len + 4 + AV_INPUT_BUFFER_PADDING_SIZE);
|
||||
if (ret < 0)
|
||||
goto fail;
|
||||
|
||||
AV_WB32(new_extradata + new_extradata_size, 1); // add the startcode
|
||||
bytestream2_get_buffer(&gb, new_extradata + new_extradata_size + 4, nalu_len);
|
||||
new_extradata_size += 4 + nalu_len;
|
||||
memset(new_extradata + new_extradata_size, 0, AV_INPUT_BUFFER_PADDING_SIZE);
|
||||
}
|
||||
}
|
||||
|
||||
av_freep(&ctx->par_out->extradata);
|
||||
ctx->par_out->extradata = new_extradata;
|
||||
ctx->par_out->extradata_size = new_extradata_size;
|
||||
|
||||
if (!new_extradata_size)
|
||||
av_log(ctx, AV_LOG_WARNING, "No parameter sets in the extradata\n");
|
||||
|
||||
return length_size;
|
||||
fail:
|
||||
av_freep(&new_extradata);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int hevc_mp4toannexb_init(AVBSFContext *ctx)
|
||||
{
|
||||
HEVCBSFContext *s = ctx->priv_data;
|
||||
int ret;
|
||||
|
||||
if (ctx->par_in->extradata_size < MIN_HEVCC_LENGTH ||
|
||||
AV_RB24(ctx->par_in->extradata) == 1 ||
|
||||
AV_RB32(ctx->par_in->extradata) == 1) {
|
||||
av_log(ctx, AV_LOG_VERBOSE,
|
||||
"The input looks like it is Annex B already\n");
|
||||
} else {
|
||||
ret = hevc_extradata_to_annexb(ctx);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
s->length_size = ret;
|
||||
s->extradata_parsed = 1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int hevc_mp4toannexb_filter(AVBSFContext *ctx, AVPacket *out)
|
||||
{
|
||||
HEVCBSFContext *s = ctx->priv_data;
|
||||
AVPacket *in;
|
||||
GetByteContext gb;
|
||||
|
||||
int got_irap = 0;
|
||||
int got_ps = 0, seen_irap_ps = 0;
|
||||
int i, ret = 0;
|
||||
|
||||
ret = ff_bsf_get_packet(ctx, &in);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
if (!s->extradata_parsed) {
|
||||
av_packet_move_ref(out, in);
|
||||
av_packet_free(&in);
|
||||
return 0;
|
||||
}
|
||||
|
||||
bytestream2_init(&gb, in->data, in->size);
|
||||
|
||||
while (!got_irap && bytestream2_get_bytes_left(&gb)) {
|
||||
uint32_t nalu_size = 0;
|
||||
int nalu_type;
|
||||
|
||||
if (bytestream2_get_bytes_left(&gb) < s->length_size) {
|
||||
ret = AVERROR_INVALIDDATA;
|
||||
goto fail;
|
||||
}
|
||||
for (i = 0; i < s->length_size; i++)
|
||||
nalu_size = (nalu_size << 8) | bytestream2_get_byte(&gb);
|
||||
|
||||
if (nalu_size < 2 || nalu_size > bytestream2_get_bytes_left(&gb)) {
|
||||
ret = AVERROR_INVALIDDATA;
|
||||
goto fail;
|
||||
}
|
||||
|
||||
nalu_type = (bytestream2_peek_byte(&gb) >> 1) & 0x3f;
|
||||
bytestream2_skip(&gb, nalu_size);
|
||||
got_irap |= nalu_type >= HEVC_NAL_BLA_W_LP &&
|
||||
nalu_type <= HEVC_NAL_RSV_IRAP_VCL23;
|
||||
got_ps |= nalu_type >= HEVC_NAL_VPS && nalu_type <= HEVC_NAL_PPS;
|
||||
}
|
||||
seen_irap_ps = got_irap && got_ps;
|
||||
got_irap = got_ps = 0;
|
||||
|
||||
bytestream2_init(&gb, in->data, in->size);
|
||||
|
||||
while (bytestream2_get_bytes_left(&gb)) {
|
||||
uint32_t nalu_size = 0;
|
||||
int nalu_type;
|
||||
int is_irap, is_ps, add_extradata, extra_size, prev_size;
|
||||
|
||||
if (bytestream2_get_bytes_left(&gb) < s->length_size) {
|
||||
ret = AVERROR_INVALIDDATA;
|
||||
goto fail;
|
||||
}
|
||||
for (i = 0; i < s->length_size; i++)
|
||||
nalu_size = (nalu_size << 8) | bytestream2_get_byte(&gb);
|
||||
|
||||
if (nalu_size < 2 || nalu_size > bytestream2_get_bytes_left(&gb)) {
|
||||
ret = AVERROR_INVALIDDATA;
|
||||
goto fail;
|
||||
}
|
||||
|
||||
nalu_type = (bytestream2_peek_byte(&gb) >> 1) & 0x3f;
|
||||
|
||||
/* prepend extradata to IRAP frames */
|
||||
is_irap = nalu_type >= HEVC_NAL_BLA_W_LP &&
|
||||
nalu_type <= HEVC_NAL_RSV_IRAP_VCL23;
|
||||
is_ps = nalu_type >= HEVC_NAL_VPS && nalu_type <= HEVC_NAL_PPS && seen_irap_ps;
|
||||
add_extradata = (is_ps || is_irap) && !got_ps && !got_irap;
|
||||
extra_size = add_extradata * ctx->par_out->extradata_size;
|
||||
got_irap |= is_irap;
|
||||
got_ps |= is_ps;
|
||||
|
||||
if (FFMIN(INT_MAX, SIZE_MAX) < 4ULL + nalu_size + extra_size) {
|
||||
ret = AVERROR_INVALIDDATA;
|
||||
goto fail;
|
||||
}
|
||||
|
||||
prev_size = out->size;
|
||||
|
||||
ret = av_grow_packet(out, 4 + nalu_size + extra_size);
|
||||
if (ret < 0)
|
||||
goto fail;
|
||||
|
||||
if (extra_size)
|
||||
memcpy(out->data + prev_size, ctx->par_out->extradata, extra_size);
|
||||
AV_WB32(out->data + prev_size + extra_size, 1);
|
||||
bytestream2_get_buffer(&gb, out->data + prev_size + 4 + extra_size, nalu_size);
|
||||
}
|
||||
|
||||
ret = av_packet_copy_props(out, in);
|
||||
if (ret < 0)
|
||||
goto fail;
|
||||
|
||||
fail:
|
||||
if (ret < 0)
|
||||
av_packet_unref(out);
|
||||
av_packet_free(&in);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static const enum AVCodecID codec_ids[] = {
|
||||
AV_CODEC_ID_HEVC, AV_CODEC_ID_NONE,
|
||||
};
|
||||
|
||||
const FFBitStreamFilter ff_hevc_mp4toannexb_bsf = {
|
||||
.p.name = "hevc_mp4toannexb",
|
||||
.p.codec_ids = codec_ids,
|
||||
.priv_data_size = sizeof(HEVCBSFContext),
|
||||
.init = hevc_mp4toannexb_init,
|
||||
.filter = hevc_mp4toannexb_filter,
|
||||
};
|
||||
@@ -0,0 +1,76 @@
|
||||
/*
|
||||
* imx dump header bitstream filter
|
||||
* Copyright (c) 2007 Baptiste Coudurier
|
||||
*
|
||||
* 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
|
||||
*/
|
||||
|
||||
/**
|
||||
* @file
|
||||
* imx dump header bitstream filter
|
||||
* modifies bitstream to fit in mov and be decoded by final cut pro decoder
|
||||
*/
|
||||
|
||||
#include "bsf.h"
|
||||
#include "bsf_internal.h"
|
||||
#include "bytestream.h"
|
||||
|
||||
|
||||
static int imx_dump_header(AVBSFContext *ctx, AVPacket *out)
|
||||
{
|
||||
/* MXF essence element key */
|
||||
static const uint8_t imx_header[16] = { 0x06,0x0e,0x2b,0x34,0x01,0x02,0x01,0x01,0x0d,0x01,0x03,0x01,0x05,0x01,0x01,0x00 };
|
||||
|
||||
AVPacket *in;
|
||||
int ret = 0;
|
||||
uint8_t *out_buf;
|
||||
|
||||
ret = ff_bsf_get_packet(ctx, &in);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
ret = av_new_packet(out, in->size + 20);
|
||||
if (ret < 0)
|
||||
goto fail;
|
||||
|
||||
out_buf = out->data;
|
||||
|
||||
bytestream_put_buffer(&out_buf, imx_header, 16);
|
||||
bytestream_put_byte(&out_buf, 0x83); /* KLV BER long form */
|
||||
bytestream_put_be24(&out_buf, in->size);
|
||||
bytestream_put_buffer(&out_buf, in->data, in->size);
|
||||
|
||||
ret = av_packet_copy_props(out, in);
|
||||
if (ret < 0)
|
||||
goto fail;
|
||||
|
||||
fail:
|
||||
if (ret < 0)
|
||||
av_packet_unref(out);
|
||||
av_packet_free(&in);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static const enum AVCodecID codec_ids[] = {
|
||||
AV_CODEC_ID_MPEG2VIDEO, AV_CODEC_ID_NONE,
|
||||
};
|
||||
|
||||
const FFBitStreamFilter ff_imx_dump_header_bsf = {
|
||||
.p.name = "imxdump",
|
||||
.p.codec_ids = codec_ids,
|
||||
.filter = imx_dump_header,
|
||||
};
|
||||
@@ -0,0 +1,169 @@
|
||||
/*
|
||||
* Media 100 to MJPEGB bitstream filter
|
||||
* Copyright (c) 2023 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
|
||||
*/
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Media 100 to MJPEGB bitstream filter.
|
||||
*/
|
||||
|
||||
#include "libavutil/intreadwrite.h"
|
||||
#include "bsf.h"
|
||||
#include "bsf_internal.h"
|
||||
#include "bytestream.h"
|
||||
|
||||
static av_cold int init(AVBSFContext *ctx)
|
||||
{
|
||||
ctx->par_out->codec_id = AV_CODEC_ID_MJPEGB;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int filter(AVBSFContext *ctx, AVPacket *out)
|
||||
{
|
||||
unsigned second_field_offset = 0;
|
||||
unsigned next_field = 0;
|
||||
unsigned dht_offset[2];
|
||||
unsigned dqt_offset[2];
|
||||
unsigned sod_offset[2];
|
||||
unsigned sof_offset[2];
|
||||
unsigned sos_offset[2];
|
||||
unsigned field = 0;
|
||||
GetByteContext gb;
|
||||
PutByteContext pb;
|
||||
AVPacket *in;
|
||||
int ret;
|
||||
|
||||
ret = ff_bsf_get_packet(ctx, &in);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
ret = av_new_packet(out, in->size + 1024);
|
||||
if (ret < 0)
|
||||
goto fail;
|
||||
|
||||
bytestream2_init(&gb, in->data, in->size);
|
||||
bytestream2_init_writer(&pb, out->data, out->size);
|
||||
|
||||
second_field:
|
||||
bytestream2_put_be32(&pb, 0);
|
||||
bytestream2_put_be32(&pb, AV_RB32("mjpg"));
|
||||
bytestream2_put_be32(&pb, 0);
|
||||
bytestream2_put_be32(&pb, 0);
|
||||
for (int i = 0; i < 6; i++)
|
||||
bytestream2_put_be32(&pb, 0);
|
||||
|
||||
sof_offset[field] = bytestream2_tell_p(&pb);
|
||||
bytestream2_put_be16(&pb, 17);
|
||||
bytestream2_put_byte(&pb, 8);
|
||||
bytestream2_put_be16(&pb, ctx->par_in->height / 2);
|
||||
bytestream2_put_be16(&pb, ctx->par_in->width);
|
||||
bytestream2_put_byte(&pb, 3);
|
||||
bytestream2_put_byte(&pb, 1);
|
||||
bytestream2_put_byte(&pb, 0x21);
|
||||
bytestream2_put_byte(&pb, 0);
|
||||
bytestream2_put_byte(&pb, 2);
|
||||
bytestream2_put_byte(&pb, 0x11);
|
||||
bytestream2_put_byte(&pb, 1);
|
||||
bytestream2_put_byte(&pb, 3);
|
||||
bytestream2_put_byte(&pb, 0x11);
|
||||
bytestream2_put_byte(&pb, 1);
|
||||
|
||||
sos_offset[field] = bytestream2_tell_p(&pb);
|
||||
bytestream2_put_be16(&pb, 12);
|
||||
bytestream2_put_byte(&pb, 3);
|
||||
bytestream2_put_byte(&pb, 1);
|
||||
bytestream2_put_byte(&pb, 0);
|
||||
bytestream2_put_byte(&pb, 2);
|
||||
bytestream2_put_byte(&pb, 0x11);
|
||||
bytestream2_put_byte(&pb, 3);
|
||||
bytestream2_put_byte(&pb, 0x11);
|
||||
bytestream2_put_byte(&pb, 0);
|
||||
bytestream2_put_byte(&pb, 0);
|
||||
bytestream2_put_byte(&pb, 0);
|
||||
|
||||
dqt_offset[field] = bytestream2_tell_p(&pb);
|
||||
bytestream2_put_be16(&pb, 132);
|
||||
bytestream2_put_byte(&pb, 0);
|
||||
bytestream2_skip(&gb, 4);
|
||||
for (int i = 0; i < 64; i++)
|
||||
bytestream2_put_byte(&pb, bytestream2_get_be32(&gb));
|
||||
bytestream2_put_byte(&pb, 1);
|
||||
for (int i = 0; i < 64; i++)
|
||||
bytestream2_put_byte(&pb, bytestream2_get_be32(&gb));
|
||||
|
||||
dht_offset[field] = 0;
|
||||
sod_offset[field] = bytestream2_tell_p(&pb);
|
||||
|
||||
for (int i = bytestream2_tell(&gb) + 8; next_field == 0 && i < in->size - 4; i++) {
|
||||
if (AV_RB32(in->data + i) == 0x00000001) {
|
||||
next_field = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
bytestream2_skip(&gb, 8);
|
||||
bytestream2_copy_buffer(&pb, &gb, next_field - bytestream2_tell(&gb));
|
||||
bytestream2_put_be64(&pb, 0);
|
||||
|
||||
if (field == 0) {
|
||||
field = 1;
|
||||
second_field_offset = bytestream2_tell_p(&pb);
|
||||
next_field = in->size;
|
||||
goto second_field;
|
||||
}
|
||||
|
||||
AV_WB32(out->data + 8, second_field_offset);
|
||||
AV_WB32(out->data + 12, second_field_offset);
|
||||
AV_WB32(out->data + 16, second_field_offset);
|
||||
AV_WB32(out->data + 20, dqt_offset[0]);
|
||||
AV_WB32(out->data + 24, dht_offset[0]);
|
||||
AV_WB32(out->data + 28, sof_offset[0]);
|
||||
AV_WB32(out->data + 32, sos_offset[0]);
|
||||
AV_WB32(out->data + 36, sod_offset[0]);
|
||||
|
||||
AV_WB32(out->data + second_field_offset + 8, bytestream2_tell_p(&pb) - second_field_offset);
|
||||
AV_WB32(out->data + second_field_offset + 12, bytestream2_tell_p(&pb) - second_field_offset);
|
||||
AV_WB32(out->data + second_field_offset + 16, 0);
|
||||
AV_WB32(out->data + second_field_offset + 20, dqt_offset[1] - second_field_offset);
|
||||
AV_WB32(out->data + second_field_offset + 24, dht_offset[1]);
|
||||
AV_WB32(out->data + second_field_offset + 28, sof_offset[1] - second_field_offset);
|
||||
AV_WB32(out->data + second_field_offset + 32, sos_offset[1] - second_field_offset);
|
||||
AV_WB32(out->data + second_field_offset + 36, sod_offset[1] - second_field_offset);
|
||||
|
||||
out->size = bytestream2_tell_p(&pb);
|
||||
memset(out->data + out->size, 0, AV_INPUT_BUFFER_PADDING_SIZE);
|
||||
|
||||
ret = av_packet_copy_props(out, in);
|
||||
if (ret < 0)
|
||||
goto fail;
|
||||
|
||||
fail:
|
||||
if (ret < 0)
|
||||
av_packet_unref(out);
|
||||
av_packet_free(&in);
|
||||
return ret;
|
||||
}
|
||||
|
||||
const FFBitStreamFilter ff_media100_to_mjpegb_bsf = {
|
||||
.p.name = "media100_to_mjpegb",
|
||||
.p.codec_ids = (const enum AVCodecID []){ AV_CODEC_ID_MEDIA100, AV_CODEC_ID_NONE },
|
||||
.init = init,
|
||||
.filter = filter,
|
||||
};
|
||||
@@ -0,0 +1,142 @@
|
||||
/*
|
||||
* MJPEG/AVI1 to JPEG/JFIF bitstream format filter
|
||||
* Copyright (c) 2010 Adrian Daerr and Nicolas George
|
||||
*
|
||||
* 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
|
||||
*/
|
||||
|
||||
/*
|
||||
* Adapted from mjpeg2jpeg.c, with original copyright:
|
||||
* Paris 2010 Adrian Daerr, public domain
|
||||
*/
|
||||
|
||||
#include <string.h>
|
||||
|
||||
#include "libavutil/error.h"
|
||||
#include "libavutil/intreadwrite.h"
|
||||
|
||||
#include "bsf.h"
|
||||
#include "bsf_internal.h"
|
||||
#include "jpegtables.h"
|
||||
#include "mjpeg.h"
|
||||
|
||||
static const uint8_t jpeg_header[] = {
|
||||
0xff, 0xd8, // SOI
|
||||
0xff, 0xe0, // APP0
|
||||
0x00, 0x10, // APP0 header size (including
|
||||
// this field, but excluding preceding)
|
||||
0x4a, 0x46, 0x49, 0x46, 0x00, // ID string 'JFIF\0'
|
||||
0x01, 0x01, // version
|
||||
0x00, // bits per type
|
||||
0x00, 0x00, // X density
|
||||
0x00, 0x00, // Y density
|
||||
0x00, // X thumbnail size
|
||||
0x00, // Y thumbnail size
|
||||
};
|
||||
|
||||
static const int dht_segment_size = 420;
|
||||
static const uint8_t dht_segment_head[] = { 0xFF, 0xC4, 0x01, 0xA2, 0x00 };
|
||||
static const uint8_t dht_segment_frag[] = {
|
||||
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09,
|
||||
0x0a, 0x0b, 0x01, 0x00, 0x03, 0x01, 0x01, 0x01, 0x01, 0x01,
|
||||
0x01, 0x01, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
};
|
||||
|
||||
static uint8_t *append(uint8_t *buf, const uint8_t *src, int size)
|
||||
{
|
||||
memcpy(buf, src, size);
|
||||
return buf + size;
|
||||
}
|
||||
|
||||
static uint8_t *append_dht_segment(uint8_t *buf)
|
||||
{
|
||||
buf = append(buf, dht_segment_head, sizeof(dht_segment_head));
|
||||
buf = append(buf, ff_mjpeg_bits_dc_luminance + 1, 16);
|
||||
buf = append(buf, dht_segment_frag, sizeof(dht_segment_frag));
|
||||
buf = append(buf, ff_mjpeg_val_dc, 12);
|
||||
*(buf++) = 0x10;
|
||||
buf = append(buf, ff_mjpeg_bits_ac_luminance + 1, 16);
|
||||
buf = append(buf, ff_mjpeg_val_ac_luminance, 162);
|
||||
*(buf++) = 0x11;
|
||||
buf = append(buf, ff_mjpeg_bits_ac_chrominance + 1, 16);
|
||||
buf = append(buf, ff_mjpeg_val_ac_chrominance, 162);
|
||||
return buf;
|
||||
}
|
||||
|
||||
static int mjpeg2jpeg_filter(AVBSFContext *ctx, AVPacket *out)
|
||||
{
|
||||
AVPacket *in;
|
||||
int ret = 0;
|
||||
int input_skip, output_size;
|
||||
uint8_t *output;
|
||||
|
||||
ret = ff_bsf_get_packet(ctx, &in);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
if (in->size < 12) {
|
||||
av_log(ctx, AV_LOG_ERROR, "input is truncated\n");
|
||||
ret = AVERROR_INVALIDDATA;
|
||||
goto fail;
|
||||
}
|
||||
if (AV_RB16(in->data) != 0xffd8) {
|
||||
av_log(ctx, AV_LOG_ERROR, "input is not MJPEG\n");
|
||||
ret = AVERROR_INVALIDDATA;
|
||||
goto fail;
|
||||
}
|
||||
if (in->data[2] == 0xff && in->data[3] == APP0) {
|
||||
input_skip = (in->data[4] << 8) + in->data[5] + 4;
|
||||
} else {
|
||||
input_skip = 2;
|
||||
}
|
||||
if (in->size < input_skip) {
|
||||
av_log(ctx, AV_LOG_ERROR, "input is truncated\n");
|
||||
ret = AVERROR_INVALIDDATA;
|
||||
goto fail;
|
||||
}
|
||||
output_size = in->size - input_skip +
|
||||
sizeof(jpeg_header) + dht_segment_size;
|
||||
ret = av_new_packet(out, output_size);
|
||||
if (ret < 0)
|
||||
goto fail;
|
||||
|
||||
output = out->data;
|
||||
|
||||
output = append(output, jpeg_header, sizeof(jpeg_header));
|
||||
output = append_dht_segment(output);
|
||||
output = append(output, in->data + input_skip, in->size - input_skip);
|
||||
|
||||
ret = av_packet_copy_props(out, in);
|
||||
if (ret < 0)
|
||||
goto fail;
|
||||
|
||||
fail:
|
||||
if (ret < 0)
|
||||
av_packet_unref(out);
|
||||
av_packet_free(&in);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static const enum AVCodecID codec_ids[] = {
|
||||
AV_CODEC_ID_MJPEG, AV_CODEC_ID_NONE,
|
||||
};
|
||||
|
||||
const FFBitStreamFilter ff_mjpeg2jpeg_bsf = {
|
||||
.p.name = "mjpeg2jpeg",
|
||||
.p.codec_ids = codec_ids,
|
||||
.filter = mjpeg2jpeg_filter,
|
||||
};
|
||||
@@ -0,0 +1,108 @@
|
||||
/*
|
||||
* MJPEG A dump header bitstream filter
|
||||
* Copyright (c) 2006 Baptiste Coudurier
|
||||
*
|
||||
* 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
|
||||
*/
|
||||
|
||||
/**
|
||||
* @file
|
||||
* MJPEG A dump header bitstream filter
|
||||
* modifies bitstream to be decoded by quicktime
|
||||
*/
|
||||
|
||||
#include "bsf.h"
|
||||
#include "bsf_internal.h"
|
||||
#include "bytestream.h"
|
||||
#include "mjpeg.h"
|
||||
|
||||
|
||||
static int mjpega_dump_header(AVBSFContext *ctx, AVPacket *out)
|
||||
{
|
||||
AVPacket *in;
|
||||
uint8_t *out_buf;
|
||||
unsigned dqt = 0, dht = 0, sof0 = 0;
|
||||
int ret = 0, i;
|
||||
|
||||
ret = ff_bsf_get_packet(ctx, &in);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
ret = av_new_packet(out, in->size + 44);
|
||||
if (ret < 0)
|
||||
goto fail;
|
||||
|
||||
ret = av_packet_copy_props(out, in);
|
||||
if (ret < 0)
|
||||
goto fail;
|
||||
|
||||
out_buf = out->data;
|
||||
bytestream_put_byte(&out_buf, 0xff);
|
||||
bytestream_put_byte(&out_buf, SOI);
|
||||
bytestream_put_byte(&out_buf, 0xff);
|
||||
bytestream_put_byte(&out_buf, APP1);
|
||||
bytestream_put_be16(&out_buf, 42); /* size */
|
||||
bytestream_put_be32(&out_buf, 0);
|
||||
bytestream_put_buffer(&out_buf, "mjpg", 4);
|
||||
bytestream_put_be32(&out_buf, in->size + 44); /* field size */
|
||||
bytestream_put_be32(&out_buf, in->size + 44); /* pad field size */
|
||||
bytestream_put_be32(&out_buf, 0); /* next ptr */
|
||||
|
||||
for (i = 0; i < in->size - 1; i++) {
|
||||
if (in->data[i] == 0xff) {
|
||||
switch (in->data[i + 1]) {
|
||||
case DQT: dqt = i + 46; break;
|
||||
case DHT: dht = i + 46; break;
|
||||
case SOF0: sof0 = i + 46; break;
|
||||
case SOS:
|
||||
bytestream_put_be32(&out_buf, dqt); /* quant off */
|
||||
bytestream_put_be32(&out_buf, dht); /* huff off */
|
||||
bytestream_put_be32(&out_buf, sof0); /* image off */
|
||||
bytestream_put_be32(&out_buf, i + 46); /* scan off */
|
||||
bytestream_put_be32(&out_buf, i + 46 + AV_RB16(in->data + i + 2)); /* data off */
|
||||
bytestream_put_buffer(&out_buf, in->data + 2, in->size - 2); /* skip already written SOI */
|
||||
|
||||
out->size = out_buf - out->data;
|
||||
av_packet_free(&in);
|
||||
return 0;
|
||||
case APP1:
|
||||
if (i + 8 < in->size && AV_RL32(in->data + i + 8) == AV_RL32("mjpg")) {
|
||||
av_log(ctx, AV_LOG_ERROR, "bitstream already formatted\n");
|
||||
av_packet_unref(out);
|
||||
av_packet_move_ref(out, in);
|
||||
av_packet_free(&in);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
av_log(ctx, AV_LOG_ERROR, "could not find SOS marker in bitstream\n");
|
||||
fail:
|
||||
av_packet_unref(out);
|
||||
av_packet_free(&in);
|
||||
return AVERROR_INVALIDDATA;
|
||||
}
|
||||
|
||||
static const enum AVCodecID codec_ids[] = {
|
||||
AV_CODEC_ID_MJPEG, AV_CODEC_ID_NONE,
|
||||
};
|
||||
|
||||
const FFBitStreamFilter ff_mjpega_dump_header_bsf = {
|
||||
.p.name = "mjpegadump",
|
||||
.p.codec_ids = codec_ids,
|
||||
.filter = mjpega_dump_header,
|
||||
};
|
||||
@@ -0,0 +1,87 @@
|
||||
/*
|
||||
* Copyright (c) 2008 Reimar Döffinger
|
||||
*
|
||||
* 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/common.h"
|
||||
#include "libavutil/intreadwrite.h"
|
||||
#include "bsf.h"
|
||||
#include "bsf_internal.h"
|
||||
|
||||
static int text2movsub(AVBSFContext *ctx, AVPacket *out)
|
||||
{
|
||||
AVPacket *in;
|
||||
int ret = 0;
|
||||
|
||||
ret = ff_bsf_get_packet(ctx, &in);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
if (in->size > 0xffff) {
|
||||
ret = AVERROR_INVALIDDATA;
|
||||
goto fail;
|
||||
}
|
||||
|
||||
ret = av_new_packet(out, in->size + 2);
|
||||
if (ret < 0) {
|
||||
ret = AVERROR(ENOMEM);
|
||||
goto fail;
|
||||
}
|
||||
|
||||
ret = av_packet_copy_props(out, in);
|
||||
if (ret < 0)
|
||||
goto fail;
|
||||
|
||||
AV_WB16(out->data, in->size);
|
||||
memcpy(out->data + 2, in->data, in->size);
|
||||
|
||||
fail:
|
||||
if (ret < 0)
|
||||
av_packet_unref(out);
|
||||
av_packet_free(&in);
|
||||
return ret;
|
||||
}
|
||||
|
||||
const FFBitStreamFilter ff_text2movsub_bsf = {
|
||||
.p.name = "text2movsub",
|
||||
.filter = text2movsub,
|
||||
};
|
||||
|
||||
static int mov2textsub(AVBSFContext *ctx, AVPacket *pkt)
|
||||
{
|
||||
int ret = 0;
|
||||
|
||||
ret = ff_bsf_get_packet_ref(ctx, pkt);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
if (pkt->size < 2) {
|
||||
av_packet_unref(pkt);
|
||||
return AVERROR_INVALIDDATA;
|
||||
}
|
||||
|
||||
pkt->size = FFMIN(pkt->size - 2, AV_RB16(pkt->data));
|
||||
pkt->data += 2;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
const FFBitStreamFilter ff_mov2textsub_bsf = {
|
||||
.p.name = "mov2textsub",
|
||||
.filter = mov2textsub,
|
||||
};
|
||||
@@ -0,0 +1,243 @@
|
||||
/*
|
||||
* 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/common.h"
|
||||
#include "libavutil/opt.h"
|
||||
|
||||
#include "bsf.h"
|
||||
#include "bsf_internal.h"
|
||||
#include "cbs.h"
|
||||
#include "cbs_bsf.h"
|
||||
#include "cbs_mpeg2.h"
|
||||
#include "mpeg12.h"
|
||||
|
||||
typedef struct MPEG2MetadataContext {
|
||||
CBSBSFContext common;
|
||||
|
||||
MPEG2RawExtensionData sequence_display_extension;
|
||||
|
||||
AVRational display_aspect_ratio;
|
||||
|
||||
AVRational frame_rate;
|
||||
|
||||
int video_format;
|
||||
int colour_primaries;
|
||||
int transfer_characteristics;
|
||||
int matrix_coefficients;
|
||||
|
||||
int mpeg1_warned;
|
||||
} MPEG2MetadataContext;
|
||||
|
||||
|
||||
static int mpeg2_metadata_update_fragment(AVBSFContext *bsf,
|
||||
AVPacket *pkt,
|
||||
CodedBitstreamFragment *frag)
|
||||
{
|
||||
MPEG2MetadataContext *ctx = bsf->priv_data;
|
||||
MPEG2RawSequenceHeader *sh = NULL;
|
||||
MPEG2RawSequenceExtension *se = NULL;
|
||||
MPEG2RawSequenceDisplayExtension *sde = NULL;
|
||||
int i, se_pos;
|
||||
|
||||
for (i = 0; i < frag->nb_units; i++) {
|
||||
if (frag->units[i].type == MPEG2_START_SEQUENCE_HEADER) {
|
||||
sh = frag->units[i].content;
|
||||
} else if (frag->units[i].type == MPEG2_START_EXTENSION) {
|
||||
MPEG2RawExtensionData *ext = frag->units[i].content;
|
||||
if (ext->extension_start_code_identifier ==
|
||||
MPEG2_EXTENSION_SEQUENCE) {
|
||||
se = &ext->data.sequence;
|
||||
se_pos = i;
|
||||
} else if (ext->extension_start_code_identifier ==
|
||||
MPEG2_EXTENSION_SEQUENCE_DISPLAY) {
|
||||
sde = &ext->data.sequence_display;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!sh || !se) {
|
||||
// No sequence header and sequence extension: not an MPEG-2 video
|
||||
// sequence.
|
||||
if (sh && !ctx->mpeg1_warned) {
|
||||
av_log(bsf, AV_LOG_WARNING, "Stream contains a sequence "
|
||||
"header but not a sequence extension: maybe it's "
|
||||
"actually MPEG-1?\n");
|
||||
ctx->mpeg1_warned = 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (ctx->display_aspect_ratio.num && ctx->display_aspect_ratio.den) {
|
||||
int num, den;
|
||||
|
||||
av_reduce(&num, &den, ctx->display_aspect_ratio.num,
|
||||
ctx->display_aspect_ratio.den, 65535);
|
||||
|
||||
if (num == 4 && den == 3)
|
||||
sh->aspect_ratio_information = 2;
|
||||
else if (num == 16 && den == 9)
|
||||
sh->aspect_ratio_information = 3;
|
||||
else if (num == 221 && den == 100)
|
||||
sh->aspect_ratio_information = 4;
|
||||
else
|
||||
sh->aspect_ratio_information = 1;
|
||||
}
|
||||
|
||||
if (ctx->frame_rate.num && ctx->frame_rate.den) {
|
||||
int code, ext_n, ext_d;
|
||||
|
||||
ff_mpeg12_find_best_frame_rate(ctx->frame_rate,
|
||||
&code, &ext_n, &ext_d, 0);
|
||||
|
||||
sh->frame_rate_code = code;
|
||||
se->frame_rate_extension_n = ext_n;
|
||||
se->frame_rate_extension_d = ext_d;
|
||||
}
|
||||
|
||||
if (ctx->video_format >= 0 ||
|
||||
ctx->colour_primaries >= 0 ||
|
||||
ctx->transfer_characteristics >= 0 ||
|
||||
ctx->matrix_coefficients >= 0) {
|
||||
if (!sde) {
|
||||
int err;
|
||||
ctx->sequence_display_extension.extension_start_code =
|
||||
MPEG2_START_EXTENSION;
|
||||
ctx->sequence_display_extension.extension_start_code_identifier =
|
||||
MPEG2_EXTENSION_SEQUENCE_DISPLAY;
|
||||
sde = &ctx->sequence_display_extension.data.sequence_display;
|
||||
|
||||
*sde = (MPEG2RawSequenceDisplayExtension) {
|
||||
.video_format = 5,
|
||||
|
||||
.colour_description = 0,
|
||||
.colour_primaries = 2,
|
||||
.transfer_characteristics = 2,
|
||||
.matrix_coefficients = 2,
|
||||
|
||||
.display_horizontal_size =
|
||||
se->horizontal_size_extension << 12 | sh->horizontal_size_value,
|
||||
.display_vertical_size =
|
||||
se->vertical_size_extension << 12 | sh->vertical_size_value,
|
||||
};
|
||||
|
||||
err = ff_cbs_insert_unit_content(frag, se_pos + 1,
|
||||
MPEG2_START_EXTENSION,
|
||||
&ctx->sequence_display_extension,
|
||||
NULL);
|
||||
if (err < 0) {
|
||||
av_log(bsf, AV_LOG_ERROR, "Failed to insert new sequence "
|
||||
"display extension.\n");
|
||||
return err;
|
||||
}
|
||||
}
|
||||
|
||||
if (ctx->video_format >= 0)
|
||||
sde->video_format = ctx->video_format;
|
||||
|
||||
if (ctx->colour_primaries >= 0 ||
|
||||
ctx->transfer_characteristics >= 0 ||
|
||||
ctx->matrix_coefficients >= 0) {
|
||||
sde->colour_description = 1;
|
||||
|
||||
if (ctx->colour_primaries >= 0)
|
||||
sde->colour_primaries = ctx->colour_primaries;
|
||||
|
||||
if (ctx->transfer_characteristics >= 0)
|
||||
sde->transfer_characteristics = ctx->transfer_characteristics;
|
||||
|
||||
if (ctx->matrix_coefficients >= 0)
|
||||
sde->matrix_coefficients = ctx->matrix_coefficients;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const CBSBSFType mpeg2_metadata_type = {
|
||||
.codec_id = AV_CODEC_ID_MPEG2VIDEO,
|
||||
.fragment_name = "frame",
|
||||
.unit_name = "start code",
|
||||
.update_fragment = &mpeg2_metadata_update_fragment,
|
||||
};
|
||||
|
||||
static int mpeg2_metadata_init(AVBSFContext *bsf)
|
||||
{
|
||||
MPEG2MetadataContext *ctx = bsf->priv_data;
|
||||
|
||||
#define VALIDITY_CHECK(name) do { \
|
||||
if (!ctx->name) { \
|
||||
av_log(bsf, AV_LOG_ERROR, "The value 0 for %s is " \
|
||||
"forbidden.\n", #name); \
|
||||
return AVERROR(EINVAL); \
|
||||
} \
|
||||
} while (0)
|
||||
VALIDITY_CHECK(colour_primaries);
|
||||
VALIDITY_CHECK(transfer_characteristics);
|
||||
VALIDITY_CHECK(matrix_coefficients);
|
||||
#undef VALIDITY_CHECK
|
||||
|
||||
return ff_cbs_bsf_generic_init(bsf, &mpeg2_metadata_type);
|
||||
}
|
||||
|
||||
#define OFFSET(x) offsetof(MPEG2MetadataContext, x)
|
||||
#define FLAGS (AV_OPT_FLAG_VIDEO_PARAM|AV_OPT_FLAG_BSF_PARAM)
|
||||
static const AVOption mpeg2_metadata_options[] = {
|
||||
{ "display_aspect_ratio", "Set display aspect ratio (table 6-3)",
|
||||
OFFSET(display_aspect_ratio), AV_OPT_TYPE_RATIONAL,
|
||||
{ .dbl = 0.0 }, 0, 65535, FLAGS },
|
||||
|
||||
{ "frame_rate", "Set frame rate",
|
||||
OFFSET(frame_rate), AV_OPT_TYPE_RATIONAL,
|
||||
{ .dbl = 0.0 }, 0, UINT_MAX, FLAGS },
|
||||
|
||||
{ "video_format", "Set video format (table 6-6)",
|
||||
OFFSET(video_format), AV_OPT_TYPE_INT,
|
||||
{ .i64 = -1 }, -1, 7, FLAGS },
|
||||
{ "colour_primaries", "Set colour primaries (table 6-7)",
|
||||
OFFSET(colour_primaries), AV_OPT_TYPE_INT,
|
||||
{ .i64 = -1 }, -1, 255, FLAGS },
|
||||
{ "transfer_characteristics", "Set transfer characteristics (table 6-8)",
|
||||
OFFSET(transfer_characteristics), AV_OPT_TYPE_INT,
|
||||
{ .i64 = -1 }, -1, 255, FLAGS },
|
||||
{ "matrix_coefficients", "Set matrix coefficients (table 6-9)",
|
||||
OFFSET(matrix_coefficients), AV_OPT_TYPE_INT,
|
||||
{ .i64 = -1 }, -1, 255, FLAGS },
|
||||
|
||||
{ NULL }
|
||||
};
|
||||
|
||||
static const AVClass mpeg2_metadata_class = {
|
||||
.class_name = "mpeg2_metadata_bsf",
|
||||
.item_name = av_default_item_name,
|
||||
.option = mpeg2_metadata_options,
|
||||
.version = LIBAVUTIL_VERSION_INT,
|
||||
};
|
||||
|
||||
static const enum AVCodecID mpeg2_metadata_codec_ids[] = {
|
||||
AV_CODEC_ID_MPEG2VIDEO, AV_CODEC_ID_NONE,
|
||||
};
|
||||
|
||||
const FFBitStreamFilter ff_mpeg2_metadata_bsf = {
|
||||
.p.name = "mpeg2_metadata",
|
||||
.p.codec_ids = mpeg2_metadata_codec_ids,
|
||||
.p.priv_class = &mpeg2_metadata_class,
|
||||
.priv_data_size = sizeof(MPEG2MetadataContext),
|
||||
.init = &mpeg2_metadata_init,
|
||||
.close = &ff_cbs_bsf_generic_close,
|
||||
.filter = &ff_cbs_bsf_generic_filter,
|
||||
};
|
||||
@@ -0,0 +1,166 @@
|
||||
/*
|
||||
* Bitstream filter for unpacking DivX-style packed B-frames in MPEG-4 (divx_packed)
|
||||
* Copyright (c) 2015 Andreas Cadhalpun <Andreas.Cadhalpun@googlemail.com>
|
||||
*
|
||||
* 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 "bsf.h"
|
||||
#include "bsf_internal.h"
|
||||
#include "mpeg4videodefs.h"
|
||||
#include "startcode.h"
|
||||
|
||||
typedef struct UnpackBFramesBSFContext {
|
||||
AVBufferRef *b_frame_ref;
|
||||
} UnpackBFramesBSFContext;
|
||||
|
||||
/* determine the position of the packed marker in the userdata,
|
||||
* the number of VOPs and the position of the second VOP */
|
||||
static void scan_buffer(const uint8_t *buf, int buf_size,
|
||||
int *pos_p, int *nb_vop, int *pos_vop2) {
|
||||
uint32_t startcode;
|
||||
const uint8_t *end = buf + buf_size, *pos = buf;
|
||||
|
||||
while (pos < end) {
|
||||
startcode = -1;
|
||||
pos = avpriv_find_start_code(pos, end, &startcode);
|
||||
|
||||
if (startcode == USER_DATA_STARTCODE && pos_p) {
|
||||
/* check if the (DivX) userdata string ends with 'p' (packed) */
|
||||
for (int i = 0; i < 255 && pos + i + 1 < end; i++) {
|
||||
if (pos[i] == 'p' && pos[i + 1] == '\0') {
|
||||
*pos_p = pos + i - buf;
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else if (startcode == VOP_STARTCODE && nb_vop) {
|
||||
*nb_vop += 1;
|
||||
if (*nb_vop == 2 && pos_vop2) {
|
||||
*pos_vop2 = pos - buf - 4; /* subtract 4 bytes startcode */
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static int mpeg4_unpack_bframes_filter(AVBSFContext *ctx, AVPacket *pkt)
|
||||
{
|
||||
UnpackBFramesBSFContext *s = ctx->priv_data;
|
||||
int pos_p = -1, nb_vop = 0, pos_vop2 = -1, ret = 0;
|
||||
|
||||
ret = ff_bsf_get_packet_ref(ctx, pkt);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
scan_buffer(pkt->data, pkt->size, &pos_p, &nb_vop, &pos_vop2);
|
||||
av_log(ctx, AV_LOG_DEBUG, "Found %d VOP startcode(s) in this packet.\n", nb_vop);
|
||||
|
||||
if (pos_vop2 >= 0) {
|
||||
if (s->b_frame_ref) {
|
||||
av_log(ctx, AV_LOG_WARNING,
|
||||
"Missing one N-VOP packet, discarding one B-frame.\n");
|
||||
av_buffer_unref(&s->b_frame_ref);
|
||||
}
|
||||
/* store a reference to the packed B-frame's data in the BSFContext */
|
||||
s->b_frame_ref = av_buffer_ref(pkt->buf);
|
||||
if (!s->b_frame_ref) {
|
||||
ret = AVERROR(ENOMEM);
|
||||
goto fail;
|
||||
}
|
||||
s->b_frame_ref->data = pkt->data + pos_vop2;
|
||||
s->b_frame_ref->size = pkt->size - pos_vop2;
|
||||
}
|
||||
|
||||
if (nb_vop > 2) {
|
||||
av_log(ctx, AV_LOG_WARNING,
|
||||
"Found %d VOP headers in one packet, only unpacking one.\n", nb_vop);
|
||||
}
|
||||
|
||||
if (nb_vop == 1 && s->b_frame_ref) {
|
||||
AVBufferRef *tmp = pkt->buf;
|
||||
|
||||
/* make tmp accurately reflect the packet's data */
|
||||
tmp->data = pkt->data;
|
||||
tmp->size = pkt->size;
|
||||
|
||||
/* replace data in packet with stored data */
|
||||
pkt->buf = s->b_frame_ref;
|
||||
pkt->data = s->b_frame_ref->data;
|
||||
pkt->size = s->b_frame_ref->size;
|
||||
|
||||
/* store reference to data into BSFContext */
|
||||
s->b_frame_ref = tmp;
|
||||
|
||||
if (s->b_frame_ref->size <= MAX_NVOP_SIZE) {
|
||||
/* N-VOP - discard stored data */
|
||||
av_log(ctx, AV_LOG_DEBUG, "Skipping N-VOP.\n");
|
||||
av_buffer_unref(&s->b_frame_ref);
|
||||
}
|
||||
} else if (nb_vop >= 2) {
|
||||
/* use first frame of the packet */
|
||||
pkt->size = pos_vop2;
|
||||
} else if (pos_p >= 0) {
|
||||
ret = av_packet_make_writable(pkt);
|
||||
if (ret < 0)
|
||||
goto fail;
|
||||
av_log(ctx, AV_LOG_DEBUG, "Updating DivX userdata (remove trailing 'p').\n");
|
||||
/* remove 'p' (packed) from the end of the (DivX) userdata string */
|
||||
pkt->data[pos_p] = '\0';
|
||||
} else {
|
||||
/* use packet as is */
|
||||
}
|
||||
|
||||
fail:
|
||||
if (ret < 0)
|
||||
av_packet_unref(pkt);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int mpeg4_unpack_bframes_init(AVBSFContext *ctx)
|
||||
{
|
||||
if (ctx->par_in->extradata) {
|
||||
int pos_p_ext = -1;
|
||||
scan_buffer(ctx->par_in->extradata, ctx->par_in->extradata_size, &pos_p_ext, NULL, NULL);
|
||||
if (pos_p_ext >= 0) {
|
||||
av_log(ctx, AV_LOG_DEBUG,
|
||||
"Updating DivX userdata (remove trailing 'p') in extradata.\n");
|
||||
ctx->par_out->extradata[pos_p_ext] = '\0';
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void mpeg4_unpack_bframes_close_flush(AVBSFContext *bsfc)
|
||||
{
|
||||
UnpackBFramesBSFContext *ctx = bsfc->priv_data;
|
||||
av_buffer_unref(&ctx->b_frame_ref);
|
||||
}
|
||||
|
||||
static const enum AVCodecID codec_ids[] = {
|
||||
AV_CODEC_ID_MPEG4, AV_CODEC_ID_NONE,
|
||||
};
|
||||
|
||||
const FFBitStreamFilter ff_mpeg4_unpack_bframes_bsf = {
|
||||
.p.name = "mpeg4_unpack_bframes",
|
||||
.p.codec_ids = codec_ids,
|
||||
.priv_data_size = sizeof(UnpackBFramesBSFContext),
|
||||
.init = mpeg4_unpack_bframes_init,
|
||||
.filter = mpeg4_unpack_bframes_filter,
|
||||
.flush = mpeg4_unpack_bframes_close_flush,
|
||||
.close = mpeg4_unpack_bframes_close_flush,
|
||||
};
|
||||
@@ -0,0 +1,236 @@
|
||||
/*
|
||||
* copyright (c) 2006 Michael Niedermayer <michaelni@gmx.at>
|
||||
*
|
||||
* 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 <stdlib.h>
|
||||
|
||||
#include "bsf.h"
|
||||
#include "bsf_internal.h"
|
||||
|
||||
#include "libavutil/log.h"
|
||||
#include "libavutil/mem.h"
|
||||
#include "libavutil/opt.h"
|
||||
#include "libavutil/eval.h"
|
||||
|
||||
static const char *const var_names[] = {
|
||||
"n", ///< packet index, starting from zero
|
||||
"tb", ///< timebase
|
||||
"pts", ///< packet presentation timestamp
|
||||
"dts", ///< packet decoding timestamp
|
||||
"nopts", ///< AV_NOPTS_VALUE
|
||||
"startpts", ///< first seen non-AV_NOPTS_VALUE packet timestamp
|
||||
"startdts", ///< first seen non-AV_NOPTS_VALUE packet timestamp
|
||||
"duration", "d", ///< packet duration
|
||||
"pos", ///< original position of packet in its source
|
||||
"size", ///< packet size
|
||||
"key" , ///< packet keyframe flag
|
||||
"state", ///< random-ish state
|
||||
NULL
|
||||
};
|
||||
|
||||
enum var_name {
|
||||
VAR_N,
|
||||
VAR_TB,
|
||||
VAR_PTS,
|
||||
VAR_DTS,
|
||||
VAR_NOPTS,
|
||||
VAR_STARTPTS,
|
||||
VAR_STARTDTS,
|
||||
VAR_DURATION, VAR_D,
|
||||
VAR_POS,
|
||||
VAR_SIZE,
|
||||
VAR_KEY,
|
||||
VAR_STATE,
|
||||
VAR_VARS_NB
|
||||
};
|
||||
|
||||
typedef struct NoiseContext {
|
||||
const AVClass *class;
|
||||
|
||||
char *amount_str;
|
||||
char *drop_str;
|
||||
int dropamount;
|
||||
|
||||
AVExpr *amount_pexpr;
|
||||
AVExpr *drop_pexpr;
|
||||
|
||||
double var_values[VAR_VARS_NB];
|
||||
|
||||
unsigned int state;
|
||||
unsigned int pkt_idx;
|
||||
} NoiseContext;
|
||||
|
||||
static int noise_init(AVBSFContext *ctx)
|
||||
{
|
||||
NoiseContext *s = ctx->priv_data;
|
||||
int ret;
|
||||
|
||||
if (!s->amount_str) {
|
||||
s->amount_str = (!s->drop_str && !s->dropamount) ? av_strdup("-1") : av_strdup("0");
|
||||
if (!s->amount_str)
|
||||
return AVERROR(ENOMEM);
|
||||
}
|
||||
|
||||
if (ctx->par_in->codec_id == AV_CODEC_ID_WRAPPED_AVFRAME &&
|
||||
strcmp(s->amount_str, "0")) {
|
||||
av_log(ctx, AV_LOG_ERROR, "Wrapped AVFrame noising is unsupported\n");
|
||||
return AVERROR_PATCHWELCOME;
|
||||
}
|
||||
|
||||
ret = av_expr_parse(&s->amount_pexpr, s->amount_str,
|
||||
var_names, NULL, NULL, NULL, NULL, 0, ctx);
|
||||
if (ret < 0) {
|
||||
av_log(ctx, AV_LOG_ERROR, "Error in parsing expr for amount: %s\n", s->amount_str);
|
||||
return ret;
|
||||
}
|
||||
|
||||
if (s->drop_str && s->dropamount) {
|
||||
av_log(ctx, AV_LOG_WARNING, "Both drop '%s' and dropamount=%d set. Ignoring dropamount.\n",
|
||||
s->drop_str, s->dropamount);
|
||||
s->dropamount = 0;
|
||||
}
|
||||
|
||||
if (s->drop_str) {
|
||||
ret = av_expr_parse(&s->drop_pexpr, s->drop_str,
|
||||
var_names, NULL, NULL, NULL, NULL, 0, ctx);
|
||||
if (ret < 0) {
|
||||
av_log(ctx, AV_LOG_ERROR, "Error in parsing expr for drop: %s\n", s->drop_str);
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
s->var_values[VAR_TB] = ctx->time_base_out.den ? av_q2d(ctx->time_base_out) : 0;
|
||||
s->var_values[VAR_NOPTS] = AV_NOPTS_VALUE;
|
||||
s->var_values[VAR_STARTPTS] = AV_NOPTS_VALUE;
|
||||
s->var_values[VAR_STARTDTS] = AV_NOPTS_VALUE;
|
||||
s->var_values[VAR_STATE] = 0;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int noise(AVBSFContext *ctx, AVPacket *pkt)
|
||||
{
|
||||
NoiseContext *s = ctx->priv_data;
|
||||
int i, ret, amount, drop = 0;
|
||||
double res;
|
||||
|
||||
ret = ff_bsf_get_packet_ref(ctx, pkt);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
s->var_values[VAR_N] = s->pkt_idx++;
|
||||
s->var_values[VAR_PTS] = pkt->pts;
|
||||
s->var_values[VAR_DTS] = pkt->dts;
|
||||
s->var_values[VAR_DURATION] =
|
||||
s->var_values[VAR_D] = pkt->duration;
|
||||
s->var_values[VAR_SIZE] = pkt->size;
|
||||
s->var_values[VAR_KEY] = !!(pkt->flags & AV_PKT_FLAG_KEY);
|
||||
s->var_values[VAR_POS] = pkt->pos;
|
||||
|
||||
if (s->var_values[VAR_STARTPTS] == AV_NOPTS_VALUE)
|
||||
s->var_values[VAR_STARTPTS] = pkt->pts;
|
||||
|
||||
if (s->var_values[VAR_STARTDTS] == AV_NOPTS_VALUE)
|
||||
s->var_values[VAR_STARTDTS] = pkt->dts;
|
||||
|
||||
res = av_expr_eval(s->amount_pexpr, s->var_values, NULL);
|
||||
|
||||
if (isnan(res))
|
||||
amount = 0;
|
||||
else if (res < 0)
|
||||
amount = (s->state % 10001 + 1);
|
||||
else
|
||||
amount = (int)res;
|
||||
|
||||
if (s->drop_str) {
|
||||
res = av_expr_eval(s->drop_pexpr, s->var_values, NULL);
|
||||
|
||||
if (isnan(res))
|
||||
drop = 0;
|
||||
else if (res < 0)
|
||||
drop = !(s->state % FFABS((int)res));
|
||||
else
|
||||
drop = !!res;
|
||||
}
|
||||
|
||||
if(s->dropamount) {
|
||||
drop = !(s->state % s->dropamount);
|
||||
}
|
||||
|
||||
av_log(ctx, AV_LOG_VERBOSE, "Stream #%d packet %d pts %"PRId64" - amount %d drop %d\n",
|
||||
pkt->stream_index, (unsigned int)s->var_values[VAR_N], pkt->pts, amount, drop);
|
||||
|
||||
if (drop) {
|
||||
s->var_values[VAR_STATE] = ++s->state;
|
||||
av_packet_unref(pkt);
|
||||
return AVERROR(EAGAIN);
|
||||
}
|
||||
|
||||
if (amount) {
|
||||
ret = av_packet_make_writable(pkt);
|
||||
if (ret < 0) {
|
||||
av_packet_unref(pkt);
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
for (i = 0; i < pkt->size; i++) {
|
||||
s->state += pkt->data[i] + 1;
|
||||
if (amount && s->state % amount == 0)
|
||||
pkt->data[i] = s->state;
|
||||
}
|
||||
|
||||
s->var_values[VAR_STATE] = s->state;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void noise_close(AVBSFContext *bsf)
|
||||
{
|
||||
NoiseContext *s = bsf->priv_data;
|
||||
|
||||
av_expr_free(s->amount_pexpr);
|
||||
av_expr_free(s->drop_pexpr);
|
||||
s->amount_pexpr = s->drop_pexpr = NULL;
|
||||
}
|
||||
|
||||
#define OFFSET(x) offsetof(NoiseContext, x)
|
||||
#define FLAGS (AV_OPT_FLAG_VIDEO_PARAM|AV_OPT_FLAG_AUDIO_PARAM|AV_OPT_FLAG_BSF_PARAM)
|
||||
static const AVOption options[] = {
|
||||
{ "amount", NULL, OFFSET(amount_str), AV_OPT_TYPE_STRING, { .str = NULL }, 0, 0, FLAGS },
|
||||
{ "drop", NULL, OFFSET(drop_str), AV_OPT_TYPE_STRING, { .str = NULL }, 0, 0, FLAGS },
|
||||
{ "dropamount", NULL, OFFSET(dropamount), AV_OPT_TYPE_INT, { .i64 = 0 }, 0, INT_MAX, FLAGS },
|
||||
{ NULL },
|
||||
};
|
||||
|
||||
static const AVClass noise_class = {
|
||||
.class_name = "noise",
|
||||
.item_name = av_default_item_name,
|
||||
.option = options,
|
||||
.version = LIBAVUTIL_VERSION_INT,
|
||||
};
|
||||
|
||||
const FFBitStreamFilter ff_noise_bsf = {
|
||||
.p.name = "noise",
|
||||
.p.priv_class = &noise_class,
|
||||
.priv_data_size = sizeof(NoiseContext),
|
||||
.init = noise_init,
|
||||
.close = noise_close,
|
||||
.filter = noise,
|
||||
};
|
||||
@@ -0,0 +1,29 @@
|
||||
/*
|
||||
* 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
|
||||
*/
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Null bitstream filter -- pass the input through unchanged.
|
||||
*/
|
||||
|
||||
#include "bsf_internal.h"
|
||||
|
||||
const FFBitStreamFilter ff_null_bsf = {
|
||||
.p.name = "null",
|
||||
.filter = ff_bsf_get_packet_ref,
|
||||
};
|
||||
@@ -0,0 +1,68 @@
|
||||
/*
|
||||
* 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 "bsf.h"
|
||||
#include "bsf_internal.h"
|
||||
#include "libavutil/intreadwrite.h"
|
||||
#include "libavutil/opt.h"
|
||||
|
||||
typedef struct OpusBSFContext {
|
||||
const AVClass *class;
|
||||
int gain;
|
||||
} OpusBSFContext;
|
||||
|
||||
static int opus_metadata_init(AVBSFContext *bsfc)
|
||||
{
|
||||
OpusBSFContext *s = bsfc->priv_data;
|
||||
|
||||
if (bsfc->par_out->extradata_size < 19)
|
||||
return AVERROR_INVALIDDATA;
|
||||
|
||||
AV_WL16(bsfc->par_out->extradata + 16, s->gain);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
#define OFFSET(x) offsetof(OpusBSFContext, x)
|
||||
#define FLAGS (AV_OPT_FLAG_AUDIO_PARAM|AV_OPT_FLAG_BSF_PARAM)
|
||||
static const AVOption opus_metadata_options[] = {
|
||||
{ "gain", "Gain, actual amplification is pow(10, gain/(20.0*256))", OFFSET(gain),
|
||||
AV_OPT_TYPE_INT, { .i64 = 0 }, -(INT16_MAX + 1), INT16_MAX, .flags = FLAGS },
|
||||
|
||||
{ NULL },
|
||||
};
|
||||
|
||||
static const AVClass opus_metadata_class = {
|
||||
.class_name = "opus_metadata_bsf",
|
||||
.item_name = av_default_item_name,
|
||||
.option = opus_metadata_options,
|
||||
.version = LIBAVUTIL_VERSION_INT,
|
||||
};
|
||||
|
||||
static const enum AVCodecID codec_ids[] = {
|
||||
AV_CODEC_ID_OPUS, AV_CODEC_ID_NONE,
|
||||
};
|
||||
|
||||
const FFBitStreamFilter ff_opus_metadata_bsf = {
|
||||
.p.name = "opus_metadata",
|
||||
.p.codec_ids = codec_ids,
|
||||
.p.priv_class = &opus_metadata_class,
|
||||
.priv_data_size = sizeof(OpusBSFContext),
|
||||
.init = &opus_metadata_init,
|
||||
.filter = &ff_bsf_get_packet_ref,
|
||||
};
|
||||
@@ -0,0 +1,241 @@
|
||||
/*
|
||||
* Copyright (c) 2020 Marton Balint
|
||||
*
|
||||
* 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 "bsf.h"
|
||||
#include "bsf_internal.h"
|
||||
#include "libavutil/avassert.h"
|
||||
#include "libavutil/opt.h"
|
||||
|
||||
typedef struct PCMContext {
|
||||
const AVClass *class;
|
||||
|
||||
int nb_out_samples;
|
||||
int pad;
|
||||
AVRational frame_rate;
|
||||
|
||||
AVPacket *in_pkt;
|
||||
AVPacket *out_pkt;
|
||||
int sample_size;
|
||||
int64_t n;
|
||||
} PCMContext;
|
||||
|
||||
static int init(AVBSFContext *ctx)
|
||||
{
|
||||
PCMContext *s = ctx->priv_data;
|
||||
AVRational sr = av_make_q(ctx->par_in->sample_rate, 1);
|
||||
int64_t min_samples;
|
||||
|
||||
if (ctx->par_in->ch_layout.nb_channels <= 0 || ctx->par_in->sample_rate <= 0)
|
||||
return AVERROR(EINVAL);
|
||||
|
||||
ctx->time_base_out = av_inv_q(sr);
|
||||
s->sample_size = ctx->par_in->ch_layout.nb_channels *
|
||||
av_get_bits_per_sample(ctx->par_in->codec_id) / 8;
|
||||
|
||||
if (s->frame_rate.num) {
|
||||
min_samples = av_rescale_q_rnd(1, sr, s->frame_rate, AV_ROUND_DOWN);
|
||||
} else {
|
||||
min_samples = s->nb_out_samples;
|
||||
}
|
||||
if (min_samples <= 0 || min_samples > INT_MAX / s->sample_size - 1)
|
||||
return AVERROR(EINVAL);
|
||||
|
||||
s->in_pkt = av_packet_alloc();
|
||||
s->out_pkt = av_packet_alloc();
|
||||
if (!s->in_pkt || !s->out_pkt)
|
||||
return AVERROR(ENOMEM);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void uninit(AVBSFContext *ctx)
|
||||
{
|
||||
PCMContext *s = ctx->priv_data;
|
||||
av_packet_free(&s->in_pkt);
|
||||
av_packet_free(&s->out_pkt);
|
||||
}
|
||||
|
||||
static void flush(AVBSFContext *ctx)
|
||||
{
|
||||
PCMContext *s = ctx->priv_data;
|
||||
av_packet_unref(s->in_pkt);
|
||||
av_packet_unref(s->out_pkt);
|
||||
s->n = 0;
|
||||
}
|
||||
|
||||
static int send_packet(PCMContext *s, int nb_samples, AVPacket *pkt)
|
||||
{
|
||||
pkt->duration = nb_samples;
|
||||
s->n++;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void drain_packet(AVPacket *pkt, int drain_data, int drain_samples)
|
||||
{
|
||||
pkt->size -= drain_data;
|
||||
pkt->data += drain_data;
|
||||
if (pkt->dts != AV_NOPTS_VALUE)
|
||||
pkt->dts += drain_samples;
|
||||
if (pkt->pts != AV_NOPTS_VALUE)
|
||||
pkt->pts += drain_samples;
|
||||
}
|
||||
|
||||
static int get_next_nb_samples(AVBSFContext *ctx)
|
||||
{
|
||||
PCMContext *s = ctx->priv_data;
|
||||
if (s->frame_rate.num) {
|
||||
AVRational sr = av_make_q(ctx->par_in->sample_rate, 1);
|
||||
return av_rescale_q(s->n + 1, sr, s->frame_rate) - av_rescale_q(s->n, sr, s->frame_rate);
|
||||
} else {
|
||||
return s->nb_out_samples;
|
||||
}
|
||||
}
|
||||
|
||||
static void set_silence(AVCodecParameters *par, uint8_t *buf, size_t size)
|
||||
{
|
||||
int c = 0;
|
||||
switch (par->codec_id) {
|
||||
case AV_CODEC_ID_PCM_ALAW: c = 0xd5; break;
|
||||
case AV_CODEC_ID_PCM_MULAW:
|
||||
case AV_CODEC_ID_PCM_VIDC: c = 0xff; break;
|
||||
case AV_CODEC_ID_PCM_U8: c = 0x80; break;
|
||||
}
|
||||
memset(buf, c, size);
|
||||
}
|
||||
|
||||
static int rechunk_filter(AVBSFContext *ctx, AVPacket *pkt)
|
||||
{
|
||||
PCMContext *s = ctx->priv_data;
|
||||
int nb_samples = get_next_nb_samples(ctx);
|
||||
int data_size = nb_samples * s->sample_size;
|
||||
int ret;
|
||||
|
||||
do {
|
||||
if (s->in_pkt->size) {
|
||||
if (s->out_pkt->size || s->in_pkt->size < data_size) {
|
||||
int drain = FFMIN(s->in_pkt->size, data_size - s->out_pkt->size);
|
||||
if (!s->out_pkt->size) {
|
||||
ret = av_new_packet(s->out_pkt, data_size);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
ret = av_packet_copy_props(s->out_pkt, s->in_pkt);
|
||||
if (ret < 0) {
|
||||
av_packet_unref(s->out_pkt);
|
||||
return ret;
|
||||
}
|
||||
s->out_pkt->size = 0;
|
||||
}
|
||||
memcpy(s->out_pkt->data + s->out_pkt->size, s->in_pkt->data, drain);
|
||||
s->out_pkt->size += drain;
|
||||
drain_packet(s->in_pkt, drain, drain / s->sample_size);
|
||||
if (!s->in_pkt->size)
|
||||
av_packet_unref(s->in_pkt);
|
||||
if (s->out_pkt->size == data_size) {
|
||||
av_packet_move_ref(pkt, s->out_pkt);
|
||||
return send_packet(s, nb_samples, pkt);
|
||||
}
|
||||
av_assert0(!s->in_pkt->size);
|
||||
} else if (s->in_pkt->size > data_size) {
|
||||
ret = av_packet_ref(pkt, s->in_pkt);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
pkt->size = data_size;
|
||||
drain_packet(s->in_pkt, data_size, nb_samples);
|
||||
return send_packet(s, nb_samples, pkt);
|
||||
} else {
|
||||
av_assert0(s->in_pkt->size == data_size);
|
||||
av_packet_move_ref(pkt, s->in_pkt);
|
||||
return send_packet(s, nb_samples, pkt);
|
||||
}
|
||||
} else
|
||||
av_packet_unref(s->in_pkt);
|
||||
|
||||
ret = ff_bsf_get_packet_ref(ctx, s->in_pkt);
|
||||
if (ret == AVERROR_EOF && s->out_pkt->size) {
|
||||
if (s->pad) {
|
||||
set_silence(ctx->par_in, s->out_pkt->data + s->out_pkt->size, data_size - s->out_pkt->size);
|
||||
s->out_pkt->size = data_size;
|
||||
} else {
|
||||
nb_samples = s->out_pkt->size / s->sample_size;
|
||||
}
|
||||
av_packet_move_ref(pkt, s->out_pkt);
|
||||
return send_packet(s, nb_samples, pkt);
|
||||
}
|
||||
if (ret >= 0)
|
||||
av_packet_rescale_ts(s->in_pkt, ctx->time_base_in, ctx->time_base_out);
|
||||
} while (ret >= 0);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
#define OFFSET(x) offsetof(PCMContext, x)
|
||||
#define FLAGS (AV_OPT_FLAG_AUDIO_PARAM | AV_OPT_FLAG_BSF_PARAM)
|
||||
static const AVOption options[] = {
|
||||
{ "nb_out_samples", "set the number of per-packet output samples", OFFSET(nb_out_samples), AV_OPT_TYPE_INT, {.i64=1024}, 1, INT_MAX, FLAGS },
|
||||
{ "n", "set the number of per-packet output samples", OFFSET(nb_out_samples), AV_OPT_TYPE_INT, {.i64=1024}, 1, INT_MAX, FLAGS },
|
||||
{ "pad", "pad last packet with zeros", OFFSET(pad), AV_OPT_TYPE_BOOL, {.i64=1} , 0, 1, FLAGS },
|
||||
{ "p", "pad last packet with zeros", OFFSET(pad), AV_OPT_TYPE_BOOL, {.i64=1} , 0, 1, FLAGS },
|
||||
{ "frame_rate", "set number of packets per second", OFFSET(frame_rate), AV_OPT_TYPE_RATIONAL, {.dbl=0}, 0, INT_MAX, FLAGS },
|
||||
{ "r", "set number of packets per second", OFFSET(frame_rate), AV_OPT_TYPE_RATIONAL, {.dbl=0}, 0, INT_MAX, FLAGS },
|
||||
{ NULL },
|
||||
};
|
||||
|
||||
static const AVClass pcm_rechunk_class = {
|
||||
.class_name = "pcm_rechunk_bsf",
|
||||
.item_name = av_default_item_name,
|
||||
.option = options,
|
||||
.version = LIBAVUTIL_VERSION_INT,
|
||||
};
|
||||
|
||||
static const enum AVCodecID codec_ids[] = {
|
||||
AV_CODEC_ID_PCM_ALAW,
|
||||
AV_CODEC_ID_PCM_F16LE,
|
||||
AV_CODEC_ID_PCM_F24LE,
|
||||
AV_CODEC_ID_PCM_F32BE,
|
||||
AV_CODEC_ID_PCM_F32LE,
|
||||
AV_CODEC_ID_PCM_F64BE,
|
||||
AV_CODEC_ID_PCM_F64LE,
|
||||
AV_CODEC_ID_PCM_MULAW,
|
||||
AV_CODEC_ID_PCM_S16BE,
|
||||
AV_CODEC_ID_PCM_S16LE,
|
||||
AV_CODEC_ID_PCM_S24BE,
|
||||
AV_CODEC_ID_PCM_S24DAUD,
|
||||
AV_CODEC_ID_PCM_S24LE,
|
||||
AV_CODEC_ID_PCM_S32BE,
|
||||
AV_CODEC_ID_PCM_S32LE,
|
||||
AV_CODEC_ID_PCM_S64BE,
|
||||
AV_CODEC_ID_PCM_S64LE,
|
||||
AV_CODEC_ID_PCM_S8,
|
||||
AV_CODEC_ID_PCM_SGA,
|
||||
AV_CODEC_ID_PCM_U8,
|
||||
AV_CODEC_ID_PCM_VIDC,
|
||||
AV_CODEC_ID_NONE,
|
||||
};
|
||||
|
||||
const FFBitStreamFilter ff_pcm_rechunk_bsf = {
|
||||
.p.name = "pcm_rechunk",
|
||||
.p.codec_ids = codec_ids,
|
||||
.p.priv_class = &pcm_rechunk_class,
|
||||
.priv_data_size = sizeof(PCMContext),
|
||||
.filter = rechunk_filter,
|
||||
.init = init,
|
||||
.flush = flush,
|
||||
.close = uninit,
|
||||
};
|
||||
@@ -0,0 +1,191 @@
|
||||
/*
|
||||
* Copyright (c) 2020 John Stebbins <jstebbins.hb@gmail.com>
|
||||
*
|
||||
* 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
|
||||
*/
|
||||
|
||||
/**
|
||||
* @file
|
||||
* This bitstream filter merges PGS subtitle packets containing incomplete
|
||||
* set of segments into a single packet
|
||||
*
|
||||
* Packets already containing a complete set of segments will be passed through
|
||||
* unchanged.
|
||||
*/
|
||||
|
||||
#include "libavutil/attributes.h"
|
||||
#include "libavutil/intreadwrite.h"
|
||||
#include "libavutil/log.h"
|
||||
#include "bsf.h"
|
||||
#include "bsf_internal.h"
|
||||
|
||||
enum PGSSegmentType {
|
||||
PALETTE_SEGMENT = 0x14,
|
||||
OBJECT_SEGMENT = 0x15,
|
||||
PRESENTATION_SEGMENT = 0x16,
|
||||
WINDOW_SEGMENT = 0x17,
|
||||
END_DISPLAY_SET_SEGMENT = 0x80,
|
||||
};
|
||||
|
||||
typedef struct PGSMergeContext {
|
||||
AVPacket *buffer_pkt, *in;
|
||||
int presentation_found;
|
||||
int pkt_flags;
|
||||
} PGSMergeContext;
|
||||
|
||||
static av_cold void frame_merge_flush(AVBSFContext *bsf)
|
||||
{
|
||||
PGSMergeContext *ctx = bsf->priv_data;
|
||||
|
||||
ctx->presentation_found = ctx->pkt_flags = 0;
|
||||
av_packet_unref(ctx->in);
|
||||
av_packet_unref(ctx->buffer_pkt);
|
||||
}
|
||||
|
||||
static int frame_merge_output(PGSMergeContext *ctx, AVPacket *dst, AVPacket *src)
|
||||
{
|
||||
if (!ctx->presentation_found)
|
||||
ctx->pkt_flags |= AV_PKT_FLAG_CORRUPT;
|
||||
ctx->presentation_found = 0;
|
||||
src->flags |= ctx->pkt_flags;
|
||||
ctx->pkt_flags = 0;
|
||||
av_packet_move_ref(dst, src);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int frame_merge_filter(AVBSFContext *bsf, AVPacket *out)
|
||||
{
|
||||
PGSMergeContext *ctx = bsf->priv_data;
|
||||
AVPacket *in = ctx->in, *pkt = ctx->buffer_pkt;
|
||||
int ret, size, pos, display = 0, presentation = 0;
|
||||
unsigned int i;
|
||||
|
||||
if (!in->data) {
|
||||
ret = ff_bsf_get_packet_ref(bsf, in);
|
||||
if (ret == AVERROR_EOF && pkt->data) {
|
||||
// Output remaining data
|
||||
ctx->pkt_flags |= AV_PKT_FLAG_CORRUPT;
|
||||
return frame_merge_output(ctx, out, pkt);
|
||||
}
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
}
|
||||
if (!in->size) {
|
||||
av_packet_unref(in);
|
||||
return AVERROR(EAGAIN);
|
||||
}
|
||||
in->flags &= ~AV_PKT_FLAG_KEY; // Will be detected in the stream
|
||||
|
||||
// Validate packet data and find display_end segment
|
||||
size = in->size;
|
||||
i = 0;
|
||||
while (i + 3 <= in->size) {
|
||||
uint8_t segment_type = in->data[i];
|
||||
int segment_len = AV_RB16(in->data + i + 1) + 3;
|
||||
|
||||
if (i + segment_len > in->size)
|
||||
break; // Invalid, segments can't span packets
|
||||
if (segment_type == PRESENTATION_SEGMENT && ctx->presentation_found)
|
||||
break; // Invalid, there can be only one
|
||||
if (segment_type == PRESENTATION_SEGMENT) {
|
||||
uint8_t state;
|
||||
if (segment_len < 11)
|
||||
break; // Invalid presentation segment length
|
||||
ctx->presentation_found = presentation = 1;
|
||||
state = in->data[i + 10] & 0xc0;
|
||||
if (state)
|
||||
ctx->pkt_flags |= AV_PKT_FLAG_KEY;
|
||||
else
|
||||
ctx->pkt_flags &= ~AV_PKT_FLAG_KEY;
|
||||
}
|
||||
i += segment_len;
|
||||
if (segment_type == END_DISPLAY_SET_SEGMENT) {
|
||||
size = i;
|
||||
display = 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (display && pkt->size == 0 && size == in->size) // passthrough
|
||||
return frame_merge_output(ctx, out, in);
|
||||
if (!display && i != in->size) {
|
||||
av_log(bsf, AV_LOG_WARNING, "Failed to parse PGS segments.\n");
|
||||
// force output what we have
|
||||
size = in->size;
|
||||
display = 1;
|
||||
ctx->pkt_flags |= AV_PKT_FLAG_CORRUPT;
|
||||
}
|
||||
|
||||
if (presentation) {
|
||||
ret = av_packet_copy_props(pkt, in);
|
||||
if (ret < 0)
|
||||
goto fail;
|
||||
}
|
||||
pos = pkt->size;
|
||||
ret = av_grow_packet(pkt, size);
|
||||
if (ret < 0)
|
||||
goto fail;
|
||||
memcpy(pkt->data + pos, in->data, size);
|
||||
|
||||
if (size == in->size)
|
||||
av_packet_unref(in);
|
||||
else {
|
||||
in->data += size;
|
||||
in->size -= size;
|
||||
}
|
||||
|
||||
if (display)
|
||||
return frame_merge_output(ctx, out, pkt);
|
||||
return AVERROR(EAGAIN);
|
||||
|
||||
fail:
|
||||
frame_merge_flush(bsf);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static av_cold int frame_merge_init(AVBSFContext *bsf)
|
||||
{
|
||||
PGSMergeContext *ctx = bsf->priv_data;
|
||||
|
||||
ctx->in = av_packet_alloc();
|
||||
ctx->buffer_pkt = av_packet_alloc();
|
||||
if (!ctx->in || !ctx->buffer_pkt)
|
||||
return AVERROR(ENOMEM);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static av_cold void frame_merge_close(AVBSFContext *bsf)
|
||||
{
|
||||
PGSMergeContext *ctx = bsf->priv_data;
|
||||
|
||||
av_packet_free(&ctx->in);
|
||||
av_packet_free(&ctx->buffer_pkt);
|
||||
}
|
||||
|
||||
static const enum AVCodecID frame_merge_codec_ids[] = {
|
||||
AV_CODEC_ID_HDMV_PGS_SUBTITLE, AV_CODEC_ID_NONE,
|
||||
};
|
||||
|
||||
const FFBitStreamFilter ff_pgs_frame_merge_bsf = {
|
||||
.p.name = "pgs_frame_merge",
|
||||
.p.codec_ids = frame_merge_codec_ids,
|
||||
.priv_data_size = sizeof(PGSMergeContext),
|
||||
.init = frame_merge_init,
|
||||
.flush = frame_merge_flush,
|
||||
.close = frame_merge_close,
|
||||
.filter = frame_merge_filter,
|
||||
};
|
||||
@@ -0,0 +1,176 @@
|
||||
/*
|
||||
* Prores Metadata bitstream filter
|
||||
* Copyright (c) 2018 Jokyo Images
|
||||
*
|
||||
* 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
|
||||
*/
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Prores Metadata bitstream filter
|
||||
* set frame colorspace property
|
||||
*/
|
||||
|
||||
#include "libavutil/common.h"
|
||||
#include "libavutil/intreadwrite.h"
|
||||
#include "libavutil/opt.h"
|
||||
|
||||
#include "bsf.h"
|
||||
#include "bsf_internal.h"
|
||||
|
||||
typedef struct ProresMetadataContext {
|
||||
const AVClass *class;
|
||||
|
||||
int color_primaries;
|
||||
int transfer_characteristics;
|
||||
int matrix_coefficients;
|
||||
} ProresMetadataContext;
|
||||
|
||||
static int prores_metadata(AVBSFContext *bsf, AVPacket *pkt)
|
||||
{
|
||||
ProresMetadataContext *ctx = bsf->priv_data;
|
||||
int ret = 0;
|
||||
int buf_size;
|
||||
uint8_t *buf;
|
||||
|
||||
ret = ff_bsf_get_packet_ref(bsf, pkt);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
ret = av_packet_make_writable(pkt);
|
||||
if (ret < 0)
|
||||
goto fail;
|
||||
|
||||
buf = pkt->data;
|
||||
buf_size = pkt->size;
|
||||
|
||||
/* check start of the prores frame */
|
||||
if (buf_size < 28) {
|
||||
av_log(bsf, AV_LOG_ERROR, "not enough data in prores frame\n");
|
||||
ret = AVERROR_INVALIDDATA;
|
||||
goto fail;
|
||||
}
|
||||
|
||||
if (AV_RL32(buf + 4) != AV_RL32("icpf")) {
|
||||
av_log(bsf, AV_LOG_ERROR, "invalid frame header\n");
|
||||
ret = AVERROR_INVALIDDATA;
|
||||
goto fail;
|
||||
}
|
||||
|
||||
if (AV_RB16(buf + 8) < 28) {
|
||||
av_log(bsf, AV_LOG_ERROR, "invalid frame header size\n");
|
||||
ret = AVERROR_INVALIDDATA;
|
||||
goto fail;
|
||||
}
|
||||
|
||||
/* set the new values */
|
||||
if (ctx->color_primaries != -1)
|
||||
buf[8+14] = ctx->color_primaries;
|
||||
if (ctx->transfer_characteristics != -1)
|
||||
buf[8+15] = ctx->transfer_characteristics;
|
||||
if (ctx->matrix_coefficients != -1)
|
||||
buf[8+16] = ctx->matrix_coefficients;
|
||||
|
||||
fail:
|
||||
if (ret < 0)
|
||||
av_packet_unref(pkt);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static const enum AVCodecID codec_ids[] = {
|
||||
AV_CODEC_ID_PRORES, AV_CODEC_ID_NONE,
|
||||
};
|
||||
|
||||
static int prores_metadata_init(AVBSFContext *bsf)
|
||||
{
|
||||
ProresMetadataContext *ctx = bsf->priv_data;
|
||||
/*! check options */
|
||||
switch (ctx->color_primaries) {
|
||||
case -1:
|
||||
case 0:
|
||||
case AVCOL_PRI_BT709:
|
||||
case AVCOL_PRI_BT470BG:
|
||||
case AVCOL_PRI_SMPTE170M:
|
||||
case AVCOL_PRI_BT2020:
|
||||
case AVCOL_PRI_SMPTE431:
|
||||
case AVCOL_PRI_SMPTE432:
|
||||
break;
|
||||
default:
|
||||
av_log(bsf, AV_LOG_ERROR, "Color primaries %d is not a valid value\n", ctx->color_primaries);
|
||||
return AVERROR(EINVAL);
|
||||
}
|
||||
|
||||
switch (ctx->matrix_coefficients) {
|
||||
case -1:
|
||||
case 0:
|
||||
case AVCOL_SPC_BT709:
|
||||
case AVCOL_SPC_SMPTE170M:
|
||||
case AVCOL_SPC_BT2020_NCL:
|
||||
break;
|
||||
default:
|
||||
av_log(bsf, AV_LOG_ERROR, "Colorspace %d is not a valid value\n", ctx->matrix_coefficients);
|
||||
return AVERROR(EINVAL);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
#define OFFSET(x) offsetof(ProresMetadataContext, x)
|
||||
#define FLAGS (AV_OPT_FLAG_VIDEO_PARAM | AV_OPT_FLAG_BSF_PARAM)
|
||||
static const AVOption options[] = {
|
||||
{"color_primaries", "select color primaries", OFFSET(color_primaries), AV_OPT_TYPE_INT, {.i64=-1}, -1, AVCOL_PRI_SMPTE432, FLAGS, .unit = "color_primaries"},
|
||||
{"auto", "keep the same color primaries", 0, AV_OPT_TYPE_CONST, {.i64=-1}, INT_MIN, INT_MAX, FLAGS, .unit = "color_primaries"},
|
||||
{"unknown", NULL, 0, AV_OPT_TYPE_CONST, {.i64=0}, INT_MIN, INT_MAX, FLAGS, .unit = "color_primaries"},
|
||||
{"bt709", NULL, 0, AV_OPT_TYPE_CONST, {.i64=AVCOL_PRI_BT709}, INT_MIN, INT_MAX, FLAGS, .unit = "color_primaries"},
|
||||
{"bt470bg", NULL, 0, AV_OPT_TYPE_CONST, {.i64=AVCOL_PRI_BT470BG}, INT_MIN, INT_MAX, FLAGS, .unit = "color_primaries"},
|
||||
{"smpte170m", NULL, 0, AV_OPT_TYPE_CONST, {.i64=AVCOL_PRI_SMPTE170M}, INT_MIN, INT_MAX, FLAGS, .unit = "color_primaries"},
|
||||
{"bt2020", NULL, 0, AV_OPT_TYPE_CONST, {.i64=AVCOL_PRI_BT2020}, INT_MIN, INT_MAX, FLAGS, .unit = "color_primaries"},
|
||||
{"smpte431", NULL, 0, AV_OPT_TYPE_CONST, {.i64=AVCOL_PRI_SMPTE431}, INT_MIN, INT_MAX, FLAGS, .unit = "color_primaries"},
|
||||
{"smpte432", NULL, 0, AV_OPT_TYPE_CONST, {.i64=AVCOL_PRI_SMPTE432}, INT_MIN, INT_MAX, FLAGS, .unit = "color_primaries"},
|
||||
|
||||
{"color_trc", "select color transfer", OFFSET(transfer_characteristics), AV_OPT_TYPE_INT, {.i64=-1}, -1, AVCOL_TRC_NB - 1, FLAGS, .unit = "color_trc"},
|
||||
{"auto", "keep the same color transfer", 0, AV_OPT_TYPE_CONST, {.i64=-1}, INT_MIN, INT_MAX, FLAGS, .unit = "color_trc"},
|
||||
{"unknown", NULL, 0, AV_OPT_TYPE_CONST, {.i64=0}, INT_MIN, INT_MAX, FLAGS, .unit = "color_trc"},
|
||||
{"bt709", NULL, 0, AV_OPT_TYPE_CONST, {.i64=AVCOL_TRC_BT709}, INT_MIN, INT_MAX, FLAGS, .unit = "color_trc"},
|
||||
{"smpte2084", NULL, 0, AV_OPT_TYPE_CONST, {.i64=AVCOL_TRC_SMPTE2084}, INT_MIN, INT_MAX, FLAGS, .unit = "color_trc"},
|
||||
{"arib-std-b67", NULL, 0, AV_OPT_TYPE_CONST, {.i64=AVCOL_TRC_ARIB_STD_B67}, INT_MIN, INT_MAX, FLAGS, .unit = "color_trc"},
|
||||
|
||||
{"colorspace", "select colorspace", OFFSET(matrix_coefficients), AV_OPT_TYPE_INT, {.i64=-1}, -1, AVCOL_SPC_BT2020_NCL, FLAGS, .unit = "colorspace"},
|
||||
{"auto", "keep the same colorspace", 0, AV_OPT_TYPE_CONST, {.i64=-1}, INT_MIN, INT_MAX, FLAGS, .unit = "colorspace"},
|
||||
{"unknown", NULL, 0, AV_OPT_TYPE_CONST, {.i64=0}, INT_MIN, INT_MAX, FLAGS, .unit = "colorspace"},
|
||||
{"bt709", NULL, 0, AV_OPT_TYPE_CONST, {.i64=AVCOL_SPC_BT709}, INT_MIN, INT_MAX, FLAGS, .unit = "colorspace"},
|
||||
{"smpte170m", NULL, 0, AV_OPT_TYPE_CONST, {.i64=AVCOL_SPC_SMPTE170M}, INT_MIN, INT_MAX, FLAGS, .unit = "colorspace"},
|
||||
{"bt2020nc", NULL, 0, AV_OPT_TYPE_CONST, {.i64=AVCOL_SPC_BT2020_NCL}, INT_MIN, INT_MAX, FLAGS, .unit = "colorspace"},
|
||||
|
||||
{ NULL },
|
||||
};
|
||||
|
||||
static const AVClass prores_metadata_class = {
|
||||
.class_name = "prores_metadata_bsf",
|
||||
.item_name = av_default_item_name,
|
||||
.option = options,
|
||||
.version = LIBAVUTIL_VERSION_INT,
|
||||
};
|
||||
|
||||
const FFBitStreamFilter ff_prores_metadata_bsf = {
|
||||
.p.name = "prores_metadata",
|
||||
.p.codec_ids = codec_ids,
|
||||
.p.priv_class = &prores_metadata_class,
|
||||
.priv_data_size = sizeof(ProresMetadataContext),
|
||||
.init = prores_metadata_init,
|
||||
.filter = prores_metadata,
|
||||
};
|
||||
@@ -0,0 +1,254 @@
|
||||
/*
|
||||
* copyright (c) 2006 Michael Niedermayer <michaelni@gmx.at>
|
||||
*
|
||||
* 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/log.h"
|
||||
#include "libavutil/opt.h"
|
||||
|
||||
#include "av1_parse.h"
|
||||
#include "bsf.h"
|
||||
#include "bsf_internal.h"
|
||||
#include "h264.h"
|
||||
#include "startcode.h"
|
||||
#include "vc1_common.h"
|
||||
|
||||
#include "hevc/hevc.h"
|
||||
|
||||
enum RemoveFreq {
|
||||
REMOVE_FREQ_KEYFRAME,
|
||||
REMOVE_FREQ_ALL,
|
||||
REMOVE_FREQ_NONKEYFRAME,
|
||||
};
|
||||
|
||||
#define START_CODE 0x000001
|
||||
|
||||
typedef struct RemoveExtradataContext {
|
||||
const AVClass *class;
|
||||
int freq;
|
||||
} RemoveExtradataContext;
|
||||
|
||||
static int av1_split(const uint8_t *buf, int buf_size, void *logctx)
|
||||
{
|
||||
AV1OBU obu;
|
||||
const uint8_t *ptr = buf, *end = buf + buf_size;
|
||||
|
||||
while (ptr < end) {
|
||||
int len = ff_av1_extract_obu(&obu, ptr, buf_size, logctx);
|
||||
if (len < 0)
|
||||
break;
|
||||
|
||||
if (obu.type == AV1_OBU_FRAME_HEADER ||
|
||||
obu.type == AV1_OBU_FRAME) {
|
||||
return ptr - buf;
|
||||
}
|
||||
ptr += len;
|
||||
buf_size -= len;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int h264_split(const uint8_t *buf, int buf_size)
|
||||
{
|
||||
const uint8_t *ptr = buf, *end = buf + buf_size;
|
||||
uint32_t state = -1;
|
||||
int has_sps = 0;
|
||||
int has_pps = 0;
|
||||
int nalu_type;
|
||||
|
||||
while (ptr < end) {
|
||||
ptr = avpriv_find_start_code(ptr, end, &state);
|
||||
if ((state & 0xFFFFFF00) != 0x100)
|
||||
break;
|
||||
nalu_type = state & 0x1F;
|
||||
if (nalu_type == H264_NAL_SPS) {
|
||||
has_sps = 1;
|
||||
} else if (nalu_type == H264_NAL_PPS)
|
||||
has_pps = 1;
|
||||
/* else if (nalu_type == 0x01 ||
|
||||
* nalu_type == 0x02 ||
|
||||
* nalu_type == 0x05) {
|
||||
* }
|
||||
*/
|
||||
else if ((nalu_type != H264_NAL_SEI || has_pps) &&
|
||||
nalu_type != H264_NAL_AUD && nalu_type != H264_NAL_SPS_EXT &&
|
||||
nalu_type != 0x0f) {
|
||||
if (has_sps) {
|
||||
while (ptr - 4 > buf && ptr[-5] == 0)
|
||||
ptr--;
|
||||
return ptr - 4 - buf;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Split after the parameter sets at the beginning of the stream if they exist.
|
||||
static int hevc_split(const uint8_t *buf, int buf_size)
|
||||
{
|
||||
const uint8_t *ptr = buf, *end = buf + buf_size;
|
||||
uint32_t state = -1;
|
||||
int has_vps = 0;
|
||||
int has_sps = 0;
|
||||
int has_pps = 0;
|
||||
int nut;
|
||||
|
||||
while (ptr < end) {
|
||||
ptr = avpriv_find_start_code(ptr, end, &state);
|
||||
if ((state >> 8) != START_CODE)
|
||||
break;
|
||||
nut = (state >> 1) & 0x3F;
|
||||
if (nut == HEVC_NAL_VPS)
|
||||
has_vps = 1;
|
||||
else if (nut == HEVC_NAL_SPS)
|
||||
has_sps = 1;
|
||||
else if (nut == HEVC_NAL_PPS)
|
||||
has_pps = 1;
|
||||
else if ((nut != HEVC_NAL_SEI_PREFIX || has_pps) &&
|
||||
nut != HEVC_NAL_AUD) {
|
||||
if (has_vps && has_sps) {
|
||||
while (ptr - 4 > buf && ptr[-5] == 0)
|
||||
ptr--;
|
||||
return ptr - 4 - buf;
|
||||
}
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int mpegvideo_split(const uint8_t *buf, int buf_size)
|
||||
{
|
||||
uint32_t state = -1;
|
||||
int found = 0;
|
||||
|
||||
for (int i = 0; i < buf_size; i++) {
|
||||
state = (state << 8) | buf[i];
|
||||
if (state == 0x1B3) {
|
||||
found = 1;
|
||||
} else if (found && state != 0x1B5 && state < 0x200 && state >= 0x100)
|
||||
return i - 3;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int mpeg4video_split(const uint8_t *buf, int buf_size)
|
||||
{
|
||||
const uint8_t *ptr = buf, *end = buf + buf_size;
|
||||
uint32_t state = -1;
|
||||
|
||||
while (ptr < end) {
|
||||
ptr = avpriv_find_start_code(ptr, end, &state);
|
||||
if (state == 0x1B3 || state == 0x1B6)
|
||||
return ptr - 4 - buf;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int vc1_split(const uint8_t *buf, int buf_size)
|
||||
{
|
||||
const uint8_t *ptr = buf, *end = buf + buf_size;
|
||||
uint32_t state = -1;
|
||||
int charged = 0;
|
||||
|
||||
while (ptr < end) {
|
||||
ptr = avpriv_find_start_code(ptr, end, &state);
|
||||
if (state == VC1_CODE_SEQHDR || state == VC1_CODE_ENTRYPOINT) {
|
||||
charged = 1;
|
||||
} else if (charged && IS_MARKER(state))
|
||||
return ptr - 4 - buf;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int remove_extradata(AVBSFContext *ctx, AVPacket *pkt)
|
||||
{
|
||||
RemoveExtradataContext *s = ctx->priv_data;
|
||||
|
||||
int ret;
|
||||
|
||||
ret = ff_bsf_get_packet_ref(ctx, pkt);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
if (s->freq == REMOVE_FREQ_ALL ||
|
||||
(s->freq == REMOVE_FREQ_NONKEYFRAME && !(pkt->flags & AV_PKT_FLAG_KEY)) ||
|
||||
(s->freq == REMOVE_FREQ_KEYFRAME && pkt->flags & AV_PKT_FLAG_KEY)) {
|
||||
int i;
|
||||
|
||||
switch (ctx->par_in->codec_id) {
|
||||
case AV_CODEC_ID_AV1:
|
||||
i = av1_split(pkt->data, pkt->size, ctx);
|
||||
break;
|
||||
case AV_CODEC_ID_AVS2:
|
||||
case AV_CODEC_ID_AVS3:
|
||||
case AV_CODEC_ID_CAVS:
|
||||
case AV_CODEC_ID_MPEG4:
|
||||
i = mpeg4video_split(pkt->data, pkt->size);
|
||||
break;
|
||||
case AV_CODEC_ID_H264:
|
||||
i = h264_split(pkt->data, pkt->size);
|
||||
break;
|
||||
case AV_CODEC_ID_HEVC:
|
||||
i = hevc_split(pkt->data, pkt->size);
|
||||
break;
|
||||
case AV_CODEC_ID_MPEG1VIDEO:
|
||||
case AV_CODEC_ID_MPEG2VIDEO:
|
||||
i = mpegvideo_split(pkt->data, pkt->size);
|
||||
break;
|
||||
case AV_CODEC_ID_VC1:
|
||||
i = vc1_split(pkt->data, pkt->size);
|
||||
break;
|
||||
default:
|
||||
i = 0;
|
||||
}
|
||||
|
||||
pkt->data += i;
|
||||
pkt->size -= i;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
#define OFFSET(x) offsetof(RemoveExtradataContext, x)
|
||||
#define FLAGS (AV_OPT_FLAG_VIDEO_PARAM|AV_OPT_FLAG_BSF_PARAM)
|
||||
static const AVOption options[] = {
|
||||
{ "freq", NULL, OFFSET(freq), AV_OPT_TYPE_INT, { .i64 = REMOVE_FREQ_KEYFRAME }, REMOVE_FREQ_KEYFRAME, REMOVE_FREQ_NONKEYFRAME, FLAGS, .unit = "freq" },
|
||||
{ "k", NULL, 0, AV_OPT_TYPE_CONST, { .i64 = REMOVE_FREQ_NONKEYFRAME }, .flags = FLAGS, .unit = "freq" },
|
||||
{ "keyframe", NULL, 0, AV_OPT_TYPE_CONST, { .i64 = REMOVE_FREQ_KEYFRAME }, .flags = FLAGS, .unit = "freq" },
|
||||
{ "e", NULL, 0, AV_OPT_TYPE_CONST, { .i64 = REMOVE_FREQ_ALL }, .flags = FLAGS, .unit = "freq" },
|
||||
{ "all", NULL, 0, AV_OPT_TYPE_CONST, { .i64 = REMOVE_FREQ_ALL }, .flags = FLAGS, .unit = "freq" },
|
||||
{ NULL },
|
||||
};
|
||||
|
||||
static const AVClass remove_extradata_class = {
|
||||
.class_name = "remove_extradata",
|
||||
.item_name = av_default_item_name,
|
||||
.option = options,
|
||||
.version = LIBAVUTIL_VERSION_INT,
|
||||
};
|
||||
|
||||
const FFBitStreamFilter ff_remove_extradata_bsf = {
|
||||
.p.name = "remove_extra",
|
||||
.p.priv_class = &remove_extradata_class,
|
||||
.priv_data_size = sizeof(RemoveExtradataContext),
|
||||
.filter = remove_extradata,
|
||||
};
|
||||
@@ -0,0 +1,277 @@
|
||||
/*
|
||||
* Copyright (c) 2021 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
|
||||
*/
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Change the PTS/DTS timestamps.
|
||||
*/
|
||||
|
||||
#include "libavutil/opt.h"
|
||||
#include "libavutil/eval.h"
|
||||
|
||||
#include "bsf.h"
|
||||
#include "bsf_internal.h"
|
||||
|
||||
static const char *const var_names[] = {
|
||||
"N", ///< frame number (starting at zero)
|
||||
"TS",
|
||||
"POS", ///< original position in the file of the frame
|
||||
"PREV_INPTS", ///< previous input PTS
|
||||
"PREV_INDTS", ///< previous input DTS
|
||||
"PREV_INDURATION", ///< previous input duration
|
||||
"PREV_OUTPTS", ///< previous output PTS
|
||||
"PREV_OUTDTS", ///< previous output DTS
|
||||
"PREV_OUTDURATION", ///< previous output duration
|
||||
"NEXT_PTS", ///< next input PTS
|
||||
"NEXT_DTS", ///< next input DTS
|
||||
"NEXT_DURATION", ///< next input duration
|
||||
"PTS", ///< original PTS in the file of the frame
|
||||
"DTS", ///< original DTS in the file of the frame
|
||||
"DURATION", ///< original duration in the file of the frame
|
||||
"STARTPTS", ///< PTS at start of movie
|
||||
"STARTDTS", ///< DTS at start of movie
|
||||
"TB", ///< input timebase of the stream
|
||||
"TB_OUT", ///< output timebase of the stream
|
||||
"SR", ///< sample rate of the stream
|
||||
"NOPTS", ///< The AV_NOPTS_VALUE constant
|
||||
NULL
|
||||
};
|
||||
|
||||
enum var_name {
|
||||
VAR_N,
|
||||
VAR_TS,
|
||||
VAR_POS,
|
||||
VAR_PREV_INPTS,
|
||||
VAR_PREV_INDTS,
|
||||
VAR_PREV_INDUR,
|
||||
VAR_PREV_OUTPTS,
|
||||
VAR_PREV_OUTDTS,
|
||||
VAR_PREV_OUTDUR,
|
||||
VAR_NEXT_PTS,
|
||||
VAR_NEXT_DTS,
|
||||
VAR_NEXT_DUR,
|
||||
VAR_PTS,
|
||||
VAR_DTS,
|
||||
VAR_DURATION,
|
||||
VAR_STARTPTS,
|
||||
VAR_STARTDTS,
|
||||
VAR_TB,
|
||||
VAR_TB_OUT,
|
||||
VAR_SR,
|
||||
VAR_NOPTS,
|
||||
VAR_VARS_NB
|
||||
};
|
||||
|
||||
typedef struct SetTSContext {
|
||||
const AVClass *class;
|
||||
|
||||
char *ts_str;
|
||||
char *pts_str;
|
||||
char *dts_str;
|
||||
char *duration_str;
|
||||
|
||||
AVRational time_base;
|
||||
|
||||
int64_t frame_number;
|
||||
|
||||
double var_values[VAR_VARS_NB];
|
||||
|
||||
AVExpr *ts_expr;
|
||||
AVExpr *pts_expr;
|
||||
AVExpr *dts_expr;
|
||||
AVExpr *duration_expr;
|
||||
|
||||
AVPacket *prev_inpkt;
|
||||
AVPacket *prev_outpkt;
|
||||
AVPacket *cur_pkt;
|
||||
} SetTSContext;
|
||||
|
||||
static int setts_init(AVBSFContext *ctx)
|
||||
{
|
||||
SetTSContext *s = ctx->priv_data;
|
||||
int ret;
|
||||
|
||||
s->prev_inpkt = av_packet_alloc();
|
||||
s->prev_outpkt = av_packet_alloc();
|
||||
s->cur_pkt = av_packet_alloc();
|
||||
if (!s->prev_inpkt || !s->prev_outpkt || !s->cur_pkt)
|
||||
return AVERROR(ENOMEM);
|
||||
|
||||
if ((ret = av_expr_parse(&s->ts_expr, s->ts_str,
|
||||
var_names, NULL, NULL, NULL, NULL, 0, ctx)) < 0) {
|
||||
av_log(ctx, AV_LOG_ERROR, "Error while parsing ts expression '%s'\n", s->ts_str);
|
||||
return ret;
|
||||
}
|
||||
|
||||
if ((ret = av_expr_parse(&s->duration_expr, s->duration_str,
|
||||
var_names, NULL, NULL, NULL, NULL, 0, ctx)) < 0) {
|
||||
av_log(ctx, AV_LOG_ERROR, "Error while parsing duration expression '%s'\n", s->duration_str);
|
||||
return ret;
|
||||
}
|
||||
|
||||
if (s->pts_str) {
|
||||
if ((ret = av_expr_parse(&s->pts_expr, s->pts_str,
|
||||
var_names, NULL, NULL, NULL, NULL, 0, ctx)) < 0) {
|
||||
av_log(ctx, AV_LOG_ERROR, "Error while parsing pts expression '%s'\n", s->pts_str);
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
if (s->dts_str) {
|
||||
if ((ret = av_expr_parse(&s->dts_expr, s->dts_str,
|
||||
var_names, NULL, NULL, NULL, NULL, 0, ctx)) < 0) {
|
||||
av_log(ctx, AV_LOG_ERROR, "Error while parsing dts expression '%s'\n", s->dts_str);
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
if (s->time_base.num > 0 && s->time_base.den > 0)
|
||||
ctx->time_base_out = s->time_base;
|
||||
|
||||
s->frame_number= 0;
|
||||
s->var_values[VAR_STARTPTS] = AV_NOPTS_VALUE;
|
||||
s->var_values[VAR_STARTDTS] = AV_NOPTS_VALUE;
|
||||
s->var_values[VAR_NOPTS] = AV_NOPTS_VALUE;
|
||||
s->var_values[VAR_TB] = ctx->time_base_in.den ? av_q2d(ctx->time_base_in) : 0;
|
||||
s->var_values[VAR_TB_OUT]= ctx->time_base_out.den ? av_q2d(ctx->time_base_out) : 0;
|
||||
s->var_values[VAR_SR] = ctx->par_in->sample_rate;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int setts_filter(AVBSFContext *ctx, AVPacket *pkt)
|
||||
{
|
||||
SetTSContext *s = ctx->priv_data;
|
||||
int64_t new_ts, new_pts, new_dts, new_duration;
|
||||
int ret;
|
||||
|
||||
ret = ff_bsf_get_packet_ref(ctx, pkt);
|
||||
if (ret < 0 && (ret != AVERROR_EOF || !s->cur_pkt->data))
|
||||
return ret;
|
||||
|
||||
if (!s->cur_pkt->data) {
|
||||
av_packet_move_ref(s->cur_pkt, pkt);
|
||||
return AVERROR(EAGAIN);
|
||||
}
|
||||
|
||||
if (s->var_values[VAR_STARTPTS] == AV_NOPTS_VALUE)
|
||||
s->var_values[VAR_STARTPTS] = s->cur_pkt->pts;
|
||||
|
||||
if (s->var_values[VAR_STARTDTS] == AV_NOPTS_VALUE)
|
||||
s->var_values[VAR_STARTDTS] = s->cur_pkt->dts;
|
||||
|
||||
s->var_values[VAR_N] = s->frame_number++;
|
||||
s->var_values[VAR_TS] = s->cur_pkt->dts;
|
||||
s->var_values[VAR_POS] = s->cur_pkt->pos;
|
||||
s->var_values[VAR_PTS] = s->cur_pkt->pts;
|
||||
s->var_values[VAR_DTS] = s->cur_pkt->dts;
|
||||
s->var_values[VAR_DURATION] = s->cur_pkt->duration;
|
||||
s->var_values[VAR_PREV_INPTS] = s->prev_inpkt->pts;
|
||||
s->var_values[VAR_PREV_INDTS] = s->prev_inpkt->dts;
|
||||
s->var_values[VAR_PREV_INDUR] = s->prev_inpkt->duration;
|
||||
s->var_values[VAR_PREV_OUTPTS] = s->prev_outpkt->pts;
|
||||
s->var_values[VAR_PREV_OUTDTS] = s->prev_outpkt->dts;
|
||||
s->var_values[VAR_PREV_OUTDUR] = s->prev_outpkt->duration;
|
||||
s->var_values[VAR_NEXT_PTS] = pkt->pts;
|
||||
s->var_values[VAR_NEXT_DTS] = pkt->dts;
|
||||
s->var_values[VAR_NEXT_DUR] = pkt->duration;
|
||||
|
||||
new_ts = llrint(av_expr_eval(s->ts_expr, s->var_values, NULL));
|
||||
new_duration = llrint(av_expr_eval(s->duration_expr, s->var_values, NULL));
|
||||
|
||||
if (s->pts_str) {
|
||||
s->var_values[VAR_TS] = s->cur_pkt->pts;
|
||||
new_pts = llrint(av_expr_eval(s->pts_expr, s->var_values, NULL));
|
||||
} else {
|
||||
new_pts = new_ts;
|
||||
}
|
||||
|
||||
if (s->dts_str) {
|
||||
s->var_values[VAR_TS] = s->cur_pkt->dts;
|
||||
new_dts = llrint(av_expr_eval(s->dts_expr, s->var_values, NULL));
|
||||
} else {
|
||||
new_dts = new_ts;
|
||||
}
|
||||
|
||||
av_packet_unref(s->prev_inpkt);
|
||||
av_packet_unref(s->prev_outpkt);
|
||||
av_packet_move_ref(s->prev_inpkt, s->cur_pkt);
|
||||
av_packet_move_ref(s->cur_pkt, pkt);
|
||||
|
||||
ret = av_packet_ref(pkt, s->prev_inpkt);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
pkt->pts = new_pts;
|
||||
pkt->dts = new_dts;
|
||||
pkt->duration = new_duration;
|
||||
|
||||
ret = av_packet_ref(s->prev_outpkt, pkt);
|
||||
if (ret < 0)
|
||||
av_packet_unref(pkt);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void setts_close(AVBSFContext *bsf)
|
||||
{
|
||||
SetTSContext *s = bsf->priv_data;
|
||||
|
||||
av_packet_free(&s->prev_inpkt);
|
||||
av_packet_free(&s->prev_outpkt);
|
||||
av_packet_free(&s->cur_pkt);
|
||||
|
||||
av_expr_free(s->ts_expr);
|
||||
s->ts_expr = NULL;
|
||||
av_expr_free(s->pts_expr);
|
||||
s->pts_expr = NULL;
|
||||
av_expr_free(s->dts_expr);
|
||||
s->dts_expr = NULL;
|
||||
av_expr_free(s->duration_expr);
|
||||
s->duration_expr = NULL;
|
||||
}
|
||||
|
||||
#define OFFSET(x) offsetof(SetTSContext, x)
|
||||
#define FLAGS (AV_OPT_FLAG_VIDEO_PARAM|AV_OPT_FLAG_AUDIO_PARAM|AV_OPT_FLAG_SUBTITLE_PARAM|AV_OPT_FLAG_BSF_PARAM)
|
||||
|
||||
static const AVOption options[] = {
|
||||
{ "ts", "set expression for packet PTS and DTS", OFFSET(ts_str), AV_OPT_TYPE_STRING, {.str="TS"}, 0, 0, FLAGS },
|
||||
{ "pts", "set expression for packet PTS", OFFSET(pts_str), AV_OPT_TYPE_STRING, {.str=NULL}, 0, 0, FLAGS },
|
||||
{ "dts", "set expression for packet DTS", OFFSET(dts_str), AV_OPT_TYPE_STRING, {.str=NULL}, 0, 0, FLAGS },
|
||||
{ "duration", "set expression for packet duration", OFFSET(duration_str), AV_OPT_TYPE_STRING, {.str="DURATION"}, 0, 0, FLAGS },
|
||||
{ "time_base", "set output timebase", OFFSET(time_base), AV_OPT_TYPE_RATIONAL, {.dbl=0}, 0, INT_MAX, FLAGS },
|
||||
{ NULL },
|
||||
};
|
||||
|
||||
static const AVClass setts_class = {
|
||||
.class_name = "setts_bsf",
|
||||
.item_name = av_default_item_name,
|
||||
.option = options,
|
||||
.version = LIBAVUTIL_VERSION_INT,
|
||||
};
|
||||
|
||||
const FFBitStreamFilter ff_setts_bsf = {
|
||||
.p.name = "setts",
|
||||
.p.priv_class = &setts_class,
|
||||
.priv_data_size = sizeof(SetTSContext),
|
||||
.init = setts_init,
|
||||
.close = setts_close,
|
||||
.filter = setts_filter,
|
||||
};
|
||||
@@ -0,0 +1,67 @@
|
||||
/*
|
||||
* Copyright (c) 2024 Anton Khirnov <anton@khirnov.net>
|
||||
*
|
||||
* 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 <inttypes.h>
|
||||
|
||||
#include "bsf.h"
|
||||
#include "bsf_internal.h"
|
||||
|
||||
#include "libavutil/adler32.h"
|
||||
#include "libavutil/log.h"
|
||||
#include "libavutil/timestamp.h"
|
||||
|
||||
typedef struct ShowinfoContext {
|
||||
uint64_t nb_packets;
|
||||
} ShowinfoContext;
|
||||
|
||||
static int showinfo_filter(AVBSFContext *ctx, AVPacket *pkt)
|
||||
{
|
||||
ShowinfoContext *priv = ctx->priv_data;
|
||||
uint32_t crc;
|
||||
int ret;
|
||||
|
||||
ret = ff_bsf_get_packet_ref(ctx, pkt);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
crc = av_adler32_update(0, pkt->data, pkt->size);
|
||||
av_log(ctx, AV_LOG_INFO,
|
||||
"n:%7"PRIu64" "
|
||||
"size:%7d "
|
||||
"pts:%s pt:%s "
|
||||
"dts:%s dt:%s "
|
||||
"ds:%"PRId64" d:%s "
|
||||
"adler32:0x%08"PRIx32
|
||||
"\n",
|
||||
priv->nb_packets, pkt->size,
|
||||
av_ts2str(pkt->pts), av_ts2timestr(pkt->pts, &ctx->time_base_in),
|
||||
av_ts2str(pkt->dts), av_ts2timestr(pkt->dts, &ctx->time_base_in),
|
||||
pkt->duration, av_ts2timestr(pkt->duration, &ctx->time_base_in), crc);
|
||||
|
||||
priv->nb_packets++;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
const FFBitStreamFilter ff_showinfo_bsf = {
|
||||
.p.name = "showinfo",
|
||||
.filter = showinfo_filter,
|
||||
.priv_data_size = sizeof(ShowinfoContext),
|
||||
};
|
||||
@@ -0,0 +1,91 @@
|
||||
/*
|
||||
* MXF SMPTE-436M ANC to EIA-608 bitstream filter
|
||||
* Copyright (c) 2025 Jacob Lifshay
|
||||
*
|
||||
* 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 "bsf.h"
|
||||
#include "bsf_internal.h"
|
||||
#include "codec_id.h"
|
||||
#include "libavcodec/smpte_436m.h"
|
||||
#include "libavutil/error.h"
|
||||
|
||||
static av_cold int ff_smpte436m_to_eia608_init(AVBSFContext *ctx)
|
||||
{
|
||||
ctx->par_out->codec_type = AVMEDIA_TYPE_SUBTITLE;
|
||||
ctx->par_out->codec_id = AV_CODEC_ID_EIA_608;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int ff_smpte436m_to_eia608_filter(AVBSFContext *ctx, AVPacket *out)
|
||||
{
|
||||
AVPacket *in;
|
||||
int ret = ff_bsf_get_packet(ctx, &in);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
AVSmpte436mAncIterator iter;
|
||||
ret = av_smpte_436m_anc_iter_init(&iter, in->data, in->size);
|
||||
if (ret < 0)
|
||||
goto fail;
|
||||
AVSmpte436mCodedAnc coded_anc;
|
||||
while ((ret = av_smpte_436m_anc_iter_next(&iter, &coded_anc)) >= 0) {
|
||||
AVSmpte291mAnc8bit anc;
|
||||
ret = av_smpte_291m_anc_8bit_decode(
|
||||
&anc, coded_anc.payload_sample_coding, coded_anc.payload_sample_count, coded_anc.payload, ctx);
|
||||
if (ret < 0)
|
||||
goto fail;
|
||||
ret = av_smpte_291m_anc_8bit_extract_cta_708(&anc, NULL, ctx);
|
||||
if (ret == AVERROR(EAGAIN))
|
||||
continue;
|
||||
if (ret < 0)
|
||||
goto fail;
|
||||
int cc_count = ret;
|
||||
|
||||
ret = av_new_packet(out, 3 * cc_count);
|
||||
if (ret < 0)
|
||||
goto fail;
|
||||
|
||||
ret = av_packet_copy_props(out, in);
|
||||
if (ret < 0)
|
||||
goto fail;
|
||||
|
||||
// verified it won't fail by running it above
|
||||
av_smpte_291m_anc_8bit_extract_cta_708(&anc, out->data, ctx);
|
||||
|
||||
av_packet_free(&in);
|
||||
|
||||
return 0;
|
||||
}
|
||||
if (ret != AVERROR_EOF)
|
||||
return ret;
|
||||
ret = AVERROR(EAGAIN);
|
||||
|
||||
fail:
|
||||
if (ret < 0)
|
||||
av_packet_unref(out);
|
||||
av_packet_free(&in);
|
||||
return ret;
|
||||
}
|
||||
|
||||
const FFBitStreamFilter ff_smpte436m_to_eia608_bsf = {
|
||||
.p.name = "smpte436m_to_eia608",
|
||||
.p.codec_ids = (const enum AVCodecID[]){ AV_CODEC_ID_SMPTE_436M_ANC, AV_CODEC_ID_NONE },
|
||||
.init = ff_smpte436m_to_eia608_init,
|
||||
.filter = ff_smpte436m_to_eia608_filter,
|
||||
};
|
||||
@@ -0,0 +1,129 @@
|
||||
/*
|
||||
* 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 <inttypes.h>
|
||||
|
||||
#include "libavutil/avstring.h"
|
||||
#include "libavutil/avutil.h"
|
||||
#include "libavutil/log.h"
|
||||
|
||||
#include "bsf.h"
|
||||
#include "bsf_internal.h"
|
||||
#include "cbs.h"
|
||||
|
||||
|
||||
typedef struct TraceHeadersContext {
|
||||
CodedBitstreamContext *cbc;
|
||||
CodedBitstreamFragment fragment;
|
||||
} TraceHeadersContext;
|
||||
|
||||
|
||||
static int trace_headers_init(AVBSFContext *bsf)
|
||||
{
|
||||
TraceHeadersContext *ctx = bsf->priv_data;
|
||||
int err;
|
||||
|
||||
err = ff_cbs_init(&ctx->cbc, bsf->par_in->codec_id, bsf);
|
||||
if (err < 0)
|
||||
return err;
|
||||
|
||||
ctx->cbc->trace_enable = 1;
|
||||
ctx->cbc->trace_level = AV_LOG_INFO;
|
||||
ctx->cbc->trace_context = ctx->cbc;
|
||||
ctx->cbc->trace_read_callback = ff_cbs_trace_read_log;
|
||||
|
||||
if (bsf->par_in->extradata) {
|
||||
CodedBitstreamFragment *frag = &ctx->fragment;
|
||||
|
||||
av_log(bsf, AV_LOG_INFO, "Extradata\n");
|
||||
|
||||
err = ff_cbs_read_extradata(ctx->cbc, frag, bsf->par_in);
|
||||
|
||||
ff_cbs_fragment_reset(frag);
|
||||
}
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
static void trace_headers_close(AVBSFContext *bsf)
|
||||
{
|
||||
TraceHeadersContext *ctx = bsf->priv_data;
|
||||
|
||||
ff_cbs_fragment_free(&ctx->fragment);
|
||||
ff_cbs_close(&ctx->cbc);
|
||||
}
|
||||
|
||||
static int trace_headers(AVBSFContext *bsf, AVPacket *pkt)
|
||||
{
|
||||
TraceHeadersContext *ctx = bsf->priv_data;
|
||||
CodedBitstreamFragment *frag = &ctx->fragment;
|
||||
char tmp[256] = { 0 };
|
||||
int err;
|
||||
|
||||
err = ff_bsf_get_packet_ref(bsf, pkt);
|
||||
if (err < 0)
|
||||
return err;
|
||||
|
||||
if (pkt->flags & AV_PKT_FLAG_KEY)
|
||||
av_strlcat(tmp, ", key frame", sizeof(tmp));
|
||||
if (pkt->flags & AV_PKT_FLAG_CORRUPT)
|
||||
av_strlcat(tmp, ", corrupt", sizeof(tmp));
|
||||
|
||||
if (pkt->pts != AV_NOPTS_VALUE)
|
||||
av_strlcatf(tmp, sizeof(tmp), ", pts %"PRId64, pkt->pts);
|
||||
else
|
||||
av_strlcat(tmp, ", no pts", sizeof(tmp));
|
||||
if (pkt->dts != AV_NOPTS_VALUE)
|
||||
av_strlcatf(tmp, sizeof(tmp), ", dts %"PRId64, pkt->dts);
|
||||
else
|
||||
av_strlcat(tmp, ", no dts", sizeof(tmp));
|
||||
if (pkt->duration > 0)
|
||||
av_strlcatf(tmp, sizeof(tmp), ", duration %"PRId64, pkt->duration);
|
||||
|
||||
av_log(bsf, AV_LOG_INFO, "Packet: %d bytes%s.\n", pkt->size, tmp);
|
||||
|
||||
if (av_packet_get_side_data(pkt, AV_PKT_DATA_NEW_EXTRADATA, NULL)) {
|
||||
av_log(bsf, AV_LOG_INFO, "Side data:\n");
|
||||
|
||||
err = ff_cbs_read_packet_side_data(ctx->cbc, frag, pkt);
|
||||
ff_cbs_fragment_reset(frag);
|
||||
|
||||
if (err < 0) {
|
||||
av_packet_unref(pkt);
|
||||
return err;
|
||||
}
|
||||
av_log(bsf, AV_LOG_INFO, "Payload:\n");
|
||||
}
|
||||
|
||||
err = ff_cbs_read_packet(ctx->cbc, frag, pkt);
|
||||
|
||||
ff_cbs_fragment_reset(frag);
|
||||
|
||||
if (err < 0)
|
||||
av_packet_unref(pkt);
|
||||
return err;
|
||||
}
|
||||
|
||||
const FFBitStreamFilter ff_trace_headers_bsf = {
|
||||
.p.name = "trace_headers",
|
||||
.p.codec_ids = ff_cbs_all_codec_ids,
|
||||
.priv_data_size = sizeof(TraceHeadersContext),
|
||||
.init = &trace_headers_init,
|
||||
.close = &trace_headers_close,
|
||||
.filter = &trace_headers,
|
||||
};
|
||||
@@ -0,0 +1,182 @@
|
||||
/*
|
||||
* 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 "bsf.h"
|
||||
#include "bsf_internal.h"
|
||||
#include "get_bits.h"
|
||||
#include "mlp_parse.h"
|
||||
#include "mlp.h"
|
||||
|
||||
typedef struct AccessUnit {
|
||||
uint8_t bits[4];
|
||||
uint16_t offset;
|
||||
uint16_t optional;
|
||||
} AccessUnit;
|
||||
|
||||
typedef struct TrueHDCoreContext {
|
||||
MLPHeaderInfo hdr;
|
||||
} TrueHDCoreContext;
|
||||
|
||||
static int truehd_core_filter(AVBSFContext *ctx, AVPacket *pkt)
|
||||
{
|
||||
TrueHDCoreContext *s = ctx->priv_data;
|
||||
GetBitContext gbc;
|
||||
AccessUnit units[MAX_SUBSTREAMS];
|
||||
int ret, i, last_offset = 0;
|
||||
int in_size, out_size;
|
||||
int have_header = 0;
|
||||
int substream_bytes = 0;
|
||||
int end;
|
||||
|
||||
ret = ff_bsf_get_packet_ref(ctx, pkt);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
if (pkt->size < 4) {
|
||||
ret = AVERROR_INVALIDDATA;
|
||||
goto fail;
|
||||
}
|
||||
|
||||
in_size = (AV_RB16(pkt->data) & 0xFFF) * 2;
|
||||
if (in_size < 4 || in_size > pkt->size) {
|
||||
ret = AVERROR_INVALIDDATA;
|
||||
goto fail;
|
||||
}
|
||||
|
||||
ret = init_get_bits8(&gbc, pkt->data + 4, pkt->size - 4);
|
||||
if (ret < 0)
|
||||
goto fail;
|
||||
|
||||
if (show_bits_long(&gbc, 32) == 0xf8726fba) {
|
||||
if ((ret = ff_mlp_read_major_sync(ctx, &s->hdr, &gbc)) < 0)
|
||||
goto fail;
|
||||
have_header = 1;
|
||||
}
|
||||
|
||||
if (s->hdr.num_substreams > MAX_SUBSTREAMS) {
|
||||
ret = AVERROR_INVALIDDATA;
|
||||
goto fail;
|
||||
}
|
||||
|
||||
for (i = 0; i < s->hdr.num_substreams; i++) {
|
||||
for (int j = 0; j < 4; j++)
|
||||
units[i].bits[j] = get_bits1(&gbc);
|
||||
|
||||
units[i].offset = get_bits(&gbc, 12);
|
||||
if (i < 3) {
|
||||
last_offset = units[i].offset * 2;
|
||||
substream_bytes += 2;
|
||||
}
|
||||
|
||||
if (units[i].bits[0]) {
|
||||
units[i].optional = get_bits(&gbc, 16);
|
||||
if (i < 3)
|
||||
substream_bytes += 2;
|
||||
}
|
||||
}
|
||||
end = get_bits_count(&gbc) >> 3;
|
||||
|
||||
out_size = end + 4 + last_offset;
|
||||
if (out_size < in_size) {
|
||||
int bpos = 0, reduce = end - have_header * 28 - substream_bytes;
|
||||
uint16_t parity_nibble, dts = AV_RB16(pkt->data + 2);
|
||||
uint16_t auheader;
|
||||
uint8_t header[28];
|
||||
|
||||
av_assert1(reduce >= 0 && reduce % 2 == 0);
|
||||
|
||||
if (have_header) {
|
||||
memcpy(header, pkt->data + 4, 28);
|
||||
header[16] = (header[16] & 0x0c) | (FFMIN(s->hdr.num_substreams, 3) << 4);
|
||||
header[17] &= 0x7f;
|
||||
header[25] &= 0xfe;
|
||||
AV_WL16(header + 26, ff_mlp_checksum16(header, 26));
|
||||
}
|
||||
|
||||
pkt->data += reduce;
|
||||
out_size -= reduce;
|
||||
pkt->size = out_size;
|
||||
|
||||
ret = av_packet_make_writable(pkt);
|
||||
if (ret < 0)
|
||||
goto fail;
|
||||
|
||||
AV_WB16(pkt->data + 2, dts);
|
||||
parity_nibble = dts;
|
||||
parity_nibble ^= out_size / 2;
|
||||
|
||||
for (i = 0; i < FFMIN(s->hdr.num_substreams, 3); i++) {
|
||||
uint16_t substr_hdr = 0;
|
||||
|
||||
substr_hdr |= (units[i].bits[0] << 15);
|
||||
substr_hdr |= (units[i].bits[1] << 14);
|
||||
substr_hdr |= (units[i].bits[2] << 13);
|
||||
substr_hdr |= (units[i].bits[3] << 12);
|
||||
substr_hdr |= units[i].offset;
|
||||
|
||||
AV_WB16(pkt->data + have_header * 28 + 4 + bpos, substr_hdr);
|
||||
|
||||
parity_nibble ^= substr_hdr;
|
||||
bpos += 2;
|
||||
|
||||
if (units[i].bits[0]) {
|
||||
AV_WB16(pkt->data + have_header * 28 + 4 + bpos, units[i].optional);
|
||||
|
||||
parity_nibble ^= units[i].optional;
|
||||
bpos += 2;
|
||||
}
|
||||
}
|
||||
|
||||
parity_nibble ^= parity_nibble >> 8;
|
||||
parity_nibble ^= parity_nibble >> 4;
|
||||
parity_nibble &= 0xF;
|
||||
|
||||
auheader = (parity_nibble ^ 0xF) << 12;
|
||||
auheader |= (out_size / 2) & 0x0fff;
|
||||
AV_WB16(pkt->data, auheader);
|
||||
|
||||
if (have_header)
|
||||
memcpy(pkt->data + 4, header, 28);
|
||||
}
|
||||
|
||||
fail:
|
||||
if (ret < 0)
|
||||
av_packet_unref(pkt);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void truehd_core_flush(AVBSFContext *ctx)
|
||||
{
|
||||
TrueHDCoreContext *s = ctx->priv_data;
|
||||
memset(&s->hdr, 0, sizeof(s->hdr));
|
||||
}
|
||||
|
||||
static const enum AVCodecID codec_ids[] = {
|
||||
AV_CODEC_ID_TRUEHD, AV_CODEC_ID_NONE,
|
||||
};
|
||||
|
||||
const FFBitStreamFilter ff_truehd_core_bsf = {
|
||||
.p.name = "truehd_core",
|
||||
.p.codec_ids = codec_ids,
|
||||
.priv_data_size = sizeof(TrueHDCoreContext),
|
||||
.filter = truehd_core_filter,
|
||||
.flush = truehd_core_flush,
|
||||
};
|
||||
@@ -0,0 +1,146 @@
|
||||
/*
|
||||
* 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/log.h"
|
||||
#include "libavutil/opt.h"
|
||||
|
||||
#include "bsf.h"
|
||||
#include "bsf_internal.h"
|
||||
#include "cbs.h"
|
||||
#include "cbs_bsf.h"
|
||||
#include "cbs_vp9.h"
|
||||
|
||||
typedef struct VP9MetadataContext {
|
||||
CBSBSFContext common;
|
||||
|
||||
int color_space;
|
||||
int color_range;
|
||||
|
||||
int color_warnings;
|
||||
} VP9MetadataContext;
|
||||
|
||||
|
||||
static int vp9_metadata_update_fragment(AVBSFContext *bsf, AVPacket *pkt,
|
||||
CodedBitstreamFragment *frag)
|
||||
{
|
||||
VP9MetadataContext *ctx = bsf->priv_data;
|
||||
int i;
|
||||
|
||||
for (i = 0; i < frag->nb_units; i++) {
|
||||
VP9RawFrame *frame = frag->units[i].content;
|
||||
VP9RawFrameHeader *header = &frame->header;
|
||||
int profile = (header->profile_high_bit << 1) + header->profile_low_bit;
|
||||
|
||||
if (header->frame_type == VP9_KEY_FRAME ||
|
||||
header->intra_only && profile > 0) {
|
||||
if (ctx->color_space >= 0) {
|
||||
if (!(profile & 1) && ctx->color_space == VP9_CS_RGB) {
|
||||
if (!(ctx->color_warnings & 2)) {
|
||||
av_log(bsf, AV_LOG_WARNING, "Warning: RGB "
|
||||
"incompatible with profiles 0 and 2.\n");
|
||||
ctx->color_warnings |= 2;
|
||||
}
|
||||
} else
|
||||
header->color_space = ctx->color_space;
|
||||
}
|
||||
|
||||
if (ctx->color_range >= 0)
|
||||
header->color_range = ctx->color_range;
|
||||
if (header->color_space == VP9_CS_RGB) {
|
||||
if (!(ctx->color_warnings & 1) && !header->color_range) {
|
||||
av_log(bsf, AV_LOG_WARNING, "Warning: Color space RGB "
|
||||
"implicitly sets color range to PC range.\n");
|
||||
ctx->color_warnings |= 1;
|
||||
}
|
||||
header->color_range = 1;
|
||||
}
|
||||
} else if (!(ctx->color_warnings & 4) && header->intra_only && !profile &&
|
||||
ctx->color_space >= 0 && ctx->color_space != VP9_CS_BT_601) {
|
||||
av_log(bsf, AV_LOG_WARNING, "Warning: Intra-only frames in "
|
||||
"profile 0 are automatically BT.601.\n");
|
||||
ctx->color_warnings |= 4;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const CBSBSFType vp9_metadata_type = {
|
||||
.codec_id = AV_CODEC_ID_VP9,
|
||||
.fragment_name = "superframe",
|
||||
.unit_name = "frame",
|
||||
.update_fragment = &vp9_metadata_update_fragment,
|
||||
};
|
||||
|
||||
static int vp9_metadata_init(AVBSFContext *bsf)
|
||||
{
|
||||
return ff_cbs_bsf_generic_init(bsf, &vp9_metadata_type);
|
||||
}
|
||||
|
||||
#define OFFSET(x) offsetof(VP9MetadataContext, x)
|
||||
#define FLAGS (AV_OPT_FLAG_VIDEO_PARAM|AV_OPT_FLAG_BSF_PARAM)
|
||||
static const AVOption vp9_metadata_options[] = {
|
||||
{ "color_space", "Set colour space (section 7.2.2)",
|
||||
OFFSET(color_space), AV_OPT_TYPE_INT,
|
||||
{ .i64 = -1 }, -1, VP9_CS_RGB, FLAGS, .unit = "cs" },
|
||||
{ "unknown", "Unknown/unspecified", 0, AV_OPT_TYPE_CONST,
|
||||
{ .i64 = VP9_CS_UNKNOWN }, .flags = FLAGS, .unit = "cs" },
|
||||
{ "bt601", "ITU-R BT.601-7", 0, AV_OPT_TYPE_CONST,
|
||||
{ .i64 = VP9_CS_BT_601 }, .flags = FLAGS, .unit = "cs" },
|
||||
{ "bt709", "ITU-R BT.709-6", 0, AV_OPT_TYPE_CONST,
|
||||
{ .i64 = VP9_CS_BT_709 }, .flags = FLAGS, .unit = "cs" },
|
||||
{ "smpte170", "SMPTE-170", 0, AV_OPT_TYPE_CONST,
|
||||
{ .i64 = VP9_CS_SMPTE_170 }, .flags = FLAGS, .unit = "cs" },
|
||||
{ "smpte240", "SMPTE-240", 0, AV_OPT_TYPE_CONST,
|
||||
{ .i64 = VP9_CS_SMPTE_240 }, .flags = FLAGS, .unit = "cs" },
|
||||
{ "bt2020", "ITU-R BT.2020-2", 0, AV_OPT_TYPE_CONST,
|
||||
{ .i64 = VP9_CS_BT_2020 }, .flags = FLAGS, .unit = "cs" },
|
||||
{ "rgb", "sRGB / IEC 61966-2-1", 0, AV_OPT_TYPE_CONST,
|
||||
{ .i64 = VP9_CS_RGB }, .flags = FLAGS, .unit = "cs" },
|
||||
|
||||
{ "color_range", "Set colour range (section 7.2.2)",
|
||||
OFFSET(color_range), AV_OPT_TYPE_INT,
|
||||
{ .i64 = -1 }, -1, 1, FLAGS, .unit = "cr" },
|
||||
{ "tv", "TV (limited) range", 0, AV_OPT_TYPE_CONST,
|
||||
{ .i64 = 0 }, .flags = FLAGS, .unit = "cr" },
|
||||
{ "pc", "PC (full) range", 0, AV_OPT_TYPE_CONST,
|
||||
{ .i64 = 1 }, .flags = FLAGS, .unit = "cr" },
|
||||
|
||||
{ NULL }
|
||||
};
|
||||
|
||||
static const AVClass vp9_metadata_class = {
|
||||
.class_name = "vp9_metadata_bsf",
|
||||
.item_name = av_default_item_name,
|
||||
.option = vp9_metadata_options,
|
||||
.version = LIBAVUTIL_VERSION_INT,
|
||||
};
|
||||
|
||||
static const enum AVCodecID vp9_metadata_codec_ids[] = {
|
||||
AV_CODEC_ID_VP9, AV_CODEC_ID_NONE,
|
||||
};
|
||||
|
||||
const FFBitStreamFilter ff_vp9_metadata_bsf = {
|
||||
.p.name = "vp9_metadata",
|
||||
.p.codec_ids = vp9_metadata_codec_ids,
|
||||
.p.priv_class = &vp9_metadata_class,
|
||||
.priv_data_size = sizeof(VP9MetadataContext),
|
||||
.init = &vp9_metadata_init,
|
||||
.close = &ff_cbs_bsf_generic_close,
|
||||
.filter = &ff_cbs_bsf_generic_filter,
|
||||
};
|
||||
@@ -0,0 +1,414 @@
|
||||
/*
|
||||
* 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/intmath.h"
|
||||
#include "libavutil/log.h"
|
||||
#include "libavutil/mem.h"
|
||||
|
||||
#include "bsf.h"
|
||||
#include "bsf_internal.h"
|
||||
#include "get_bits.h"
|
||||
#include "put_bits.h"
|
||||
|
||||
#define FRAME_SLOTS 8
|
||||
|
||||
typedef struct VP9RawReorderFrame {
|
||||
AVPacket *packet;
|
||||
int needs_output;
|
||||
int needs_display;
|
||||
|
||||
int64_t pts;
|
||||
int64_t sequence;
|
||||
unsigned int slots;
|
||||
|
||||
unsigned int profile;
|
||||
|
||||
unsigned int show_existing_frame;
|
||||
unsigned int frame_to_show;
|
||||
|
||||
unsigned int frame_type;
|
||||
unsigned int show_frame;
|
||||
unsigned int refresh_frame_flags;
|
||||
} VP9RawReorderFrame;
|
||||
|
||||
typedef struct VP9RawReorderContext {
|
||||
int64_t sequence;
|
||||
VP9RawReorderFrame *slot[FRAME_SLOTS];
|
||||
VP9RawReorderFrame *next_frame;
|
||||
} VP9RawReorderContext;
|
||||
|
||||
static void vp9_raw_reorder_frame_free(VP9RawReorderFrame **frame)
|
||||
{
|
||||
if (*frame)
|
||||
av_packet_free(&(*frame)->packet);
|
||||
av_freep(frame);
|
||||
}
|
||||
|
||||
static void vp9_raw_reorder_clear_slot(VP9RawReorderContext *ctx, int s)
|
||||
{
|
||||
if (ctx->slot[s]) {
|
||||
ctx->slot[s]->slots &= ~(1 << s);
|
||||
if (ctx->slot[s]->slots == 0)
|
||||
vp9_raw_reorder_frame_free(&ctx->slot[s]);
|
||||
else
|
||||
ctx->slot[s] = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
static int vp9_raw_reorder_frame_parse(AVBSFContext *bsf, VP9RawReorderFrame *frame)
|
||||
{
|
||||
GetBitContext bc;
|
||||
int err;
|
||||
|
||||
unsigned int frame_marker;
|
||||
unsigned int profile_low_bit, profile_high_bit, reserved_zero;
|
||||
unsigned int error_resilient_mode;
|
||||
unsigned int frame_sync_code;
|
||||
|
||||
err = init_get_bits(&bc, frame->packet->data, 8 * frame->packet->size);
|
||||
if (err)
|
||||
return err;
|
||||
|
||||
frame_marker = get_bits(&bc, 2);
|
||||
if (frame_marker != 2) {
|
||||
av_log(bsf, AV_LOG_ERROR, "Invalid frame marker: %u.\n",
|
||||
frame_marker);
|
||||
return AVERROR_INVALIDDATA;
|
||||
}
|
||||
|
||||
profile_low_bit = get_bits1(&bc);
|
||||
profile_high_bit = get_bits1(&bc);
|
||||
frame->profile = (profile_high_bit << 1) | profile_low_bit;
|
||||
if (frame->profile == 3) {
|
||||
reserved_zero = get_bits1(&bc);
|
||||
if (reserved_zero != 0) {
|
||||
av_log(bsf, AV_LOG_ERROR, "Profile reserved_zero bit set: "
|
||||
"unsupported profile or invalid bitstream.\n");
|
||||
return AVERROR_INVALIDDATA;
|
||||
}
|
||||
}
|
||||
|
||||
frame->show_existing_frame = get_bits1(&bc);
|
||||
if (frame->show_existing_frame) {
|
||||
frame->frame_to_show = get_bits(&bc, 3);
|
||||
return 0;
|
||||
}
|
||||
|
||||
frame->frame_type = get_bits1(&bc);
|
||||
frame->show_frame = get_bits1(&bc);
|
||||
error_resilient_mode = get_bits1(&bc);
|
||||
|
||||
if (frame->frame_type == 0) {
|
||||
frame_sync_code = get_bits(&bc, 24);
|
||||
if (frame_sync_code != 0x498342) {
|
||||
av_log(bsf, AV_LOG_ERROR, "Invalid frame sync code: %06x.\n",
|
||||
frame_sync_code);
|
||||
return AVERROR_INVALIDDATA;
|
||||
}
|
||||
frame->refresh_frame_flags = 0xff;
|
||||
} else {
|
||||
unsigned int intra_only;
|
||||
|
||||
if (frame->show_frame == 0)
|
||||
intra_only = get_bits1(&bc);
|
||||
else
|
||||
intra_only = 0;
|
||||
if (error_resilient_mode == 0) {
|
||||
// reset_frame_context
|
||||
skip_bits(&bc, 2);
|
||||
}
|
||||
if (intra_only) {
|
||||
frame_sync_code = get_bits(&bc, 24);
|
||||
if (frame_sync_code != 0x498342) {
|
||||
av_log(bsf, AV_LOG_ERROR, "Invalid frame sync code: "
|
||||
"%06x.\n", frame_sync_code);
|
||||
return AVERROR_INVALIDDATA;
|
||||
}
|
||||
if (frame->profile > 0) {
|
||||
unsigned int color_space;
|
||||
if (frame->profile >= 2) {
|
||||
// ten_or_twelve_bit
|
||||
skip_bits(&bc, 1);
|
||||
}
|
||||
color_space = get_bits(&bc, 3);
|
||||
if (color_space != 7 /* CS_RGB */) {
|
||||
// color_range
|
||||
skip_bits(&bc, 1);
|
||||
if (frame->profile == 1 || frame->profile == 3) {
|
||||
// subsampling
|
||||
skip_bits(&bc, 3);
|
||||
}
|
||||
} else {
|
||||
if (frame->profile == 1 || frame->profile == 3)
|
||||
skip_bits(&bc, 1);
|
||||
}
|
||||
}
|
||||
frame->refresh_frame_flags = get_bits(&bc, 8);
|
||||
} else {
|
||||
frame->refresh_frame_flags = get_bits(&bc, 8);
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int vp9_raw_reorder_make_output(AVBSFContext *bsf,
|
||||
AVPacket *out,
|
||||
VP9RawReorderFrame *last_frame)
|
||||
{
|
||||
VP9RawReorderContext *ctx = bsf->priv_data;
|
||||
VP9RawReorderFrame *next_output = last_frame,
|
||||
*next_display = last_frame, *frame;
|
||||
int s, err;
|
||||
|
||||
for (s = 0; s < FRAME_SLOTS; s++) {
|
||||
frame = ctx->slot[s];
|
||||
if (!frame)
|
||||
continue;
|
||||
if (frame->needs_output && (!next_output ||
|
||||
frame->sequence < next_output->sequence))
|
||||
next_output = frame;
|
||||
if (frame->needs_display && (!next_display ||
|
||||
frame->pts < next_display->pts))
|
||||
next_display = frame;
|
||||
}
|
||||
|
||||
if (!next_output && !next_display)
|
||||
return AVERROR_EOF;
|
||||
|
||||
if (!next_display || (next_output &&
|
||||
next_output->sequence < next_display->sequence))
|
||||
frame = next_output;
|
||||
else
|
||||
frame = next_display;
|
||||
|
||||
if (frame->needs_output && frame->needs_display &&
|
||||
next_output == next_display) {
|
||||
av_log(bsf, AV_LOG_DEBUG, "Output and display frame "
|
||||
"%"PRId64" (%"PRId64") in order.\n",
|
||||
frame->sequence, frame->pts);
|
||||
|
||||
av_packet_move_ref(out, frame->packet);
|
||||
|
||||
frame->needs_output = frame->needs_display = 0;
|
||||
} else if (frame->needs_output) {
|
||||
if (frame->needs_display) {
|
||||
av_log(bsf, AV_LOG_DEBUG, "Output frame %"PRId64" "
|
||||
"(%"PRId64") for later display.\n",
|
||||
frame->sequence, frame->pts);
|
||||
} else {
|
||||
av_log(bsf, AV_LOG_DEBUG, "Output unshown frame "
|
||||
"%"PRId64" (%"PRId64") to keep order.\n",
|
||||
frame->sequence, frame->pts);
|
||||
}
|
||||
|
||||
av_packet_move_ref(out, frame->packet);
|
||||
out->pts = out->dts;
|
||||
|
||||
frame->needs_output = 0;
|
||||
} else {
|
||||
PutBitContext pb;
|
||||
|
||||
av_assert0(!frame->needs_output && frame->needs_display);
|
||||
|
||||
if (frame->slots == 0) {
|
||||
av_log(bsf, AV_LOG_ERROR, "Attempting to display frame "
|
||||
"which is no longer available?\n");
|
||||
frame->needs_display = 0;
|
||||
return AVERROR_INVALIDDATA;
|
||||
}
|
||||
|
||||
s = ff_ctz(frame->slots);
|
||||
av_assert0(s < FRAME_SLOTS);
|
||||
|
||||
av_log(bsf, AV_LOG_DEBUG, "Display frame %"PRId64" "
|
||||
"(%"PRId64") from slot %d.\n",
|
||||
frame->sequence, frame->pts, s);
|
||||
|
||||
err = av_new_packet(out, 2);
|
||||
if (err < 0)
|
||||
return err;
|
||||
|
||||
init_put_bits(&pb, out->data, 2);
|
||||
|
||||
// frame_marker
|
||||
put_bits(&pb, 2, 2);
|
||||
// profile_low_bit
|
||||
put_bits(&pb, 1, frame->profile & 1);
|
||||
// profile_high_bit
|
||||
put_bits(&pb, 1, (frame->profile >> 1) & 1);
|
||||
if (frame->profile == 3) {
|
||||
// reserved_zero
|
||||
put_bits(&pb, 1, 0);
|
||||
}
|
||||
// show_existing_frame
|
||||
put_bits(&pb, 1, 1);
|
||||
// frame_to_show_map_idx
|
||||
put_bits(&pb, 3, s);
|
||||
|
||||
while (put_bits_count(&pb) < 16)
|
||||
put_bits(&pb, 1, 0);
|
||||
|
||||
flush_put_bits(&pb);
|
||||
out->pts = out->dts = frame->pts;
|
||||
|
||||
frame->needs_display = 0;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int vp9_raw_reorder_filter(AVBSFContext *bsf, AVPacket *out)
|
||||
{
|
||||
VP9RawReorderContext *ctx = bsf->priv_data;
|
||||
VP9RawReorderFrame *frame;
|
||||
AVPacket *in;
|
||||
int err, s;
|
||||
|
||||
if (ctx->next_frame) {
|
||||
frame = ctx->next_frame;
|
||||
|
||||
} else {
|
||||
err = ff_bsf_get_packet(bsf, &in);
|
||||
if (err < 0) {
|
||||
if (err == AVERROR_EOF)
|
||||
return vp9_raw_reorder_make_output(bsf, out, NULL);
|
||||
return err;
|
||||
}
|
||||
|
||||
if (!in->size) {
|
||||
av_packet_free(&in);
|
||||
return AVERROR_INVALIDDATA;
|
||||
}
|
||||
|
||||
if ((in->data[in->size - 1] & 0xe0) == 0xc0) {
|
||||
av_log(bsf, AV_LOG_ERROR, "Input in superframes is not "
|
||||
"supported.\n");
|
||||
av_packet_free(&in);
|
||||
return AVERROR(ENOSYS);
|
||||
}
|
||||
|
||||
frame = av_mallocz(sizeof(*frame));
|
||||
if (!frame) {
|
||||
av_packet_free(&in);
|
||||
return AVERROR(ENOMEM);
|
||||
}
|
||||
|
||||
frame->packet = in;
|
||||
frame->pts = in->pts;
|
||||
frame->sequence = ++ctx->sequence;
|
||||
err = vp9_raw_reorder_frame_parse(bsf, frame);
|
||||
if (err) {
|
||||
av_log(bsf, AV_LOG_ERROR, "Failed to parse input "
|
||||
"frame: %d.\n", err);
|
||||
goto fail;
|
||||
}
|
||||
|
||||
frame->needs_output = 1;
|
||||
frame->needs_display = frame->pts != AV_NOPTS_VALUE;
|
||||
|
||||
if (frame->show_existing_frame)
|
||||
av_log(bsf, AV_LOG_DEBUG, "Show frame %"PRId64" "
|
||||
"(%"PRId64"): show %u.\n", frame->sequence,
|
||||
frame->pts, frame->frame_to_show);
|
||||
else
|
||||
av_log(bsf, AV_LOG_DEBUG, "New frame %"PRId64" "
|
||||
"(%"PRId64"): type %u show %u refresh %02x.\n",
|
||||
frame->sequence, frame->pts, frame->frame_type,
|
||||
frame->show_frame, frame->refresh_frame_flags);
|
||||
|
||||
ctx->next_frame = frame;
|
||||
}
|
||||
|
||||
for (s = 0; s < FRAME_SLOTS; s++) {
|
||||
if (!(frame->refresh_frame_flags & (1 << s)))
|
||||
continue;
|
||||
if (ctx->slot[s] && ctx->slot[s]->needs_display &&
|
||||
ctx->slot[s]->slots == (1 << s)) {
|
||||
// We are overwriting this slot, which is last reference
|
||||
// to the frame previously present in it. In order to be
|
||||
// a valid stream, that frame must already have been
|
||||
// displayed before the pts of the current frame.
|
||||
err = vp9_raw_reorder_make_output(bsf, out, ctx->slot[s]);
|
||||
if (err < 0) {
|
||||
av_log(bsf, AV_LOG_ERROR, "Failed to create "
|
||||
"output overwriting slot %d: %d.\n",
|
||||
s, err);
|
||||
// Clear the slot anyway, so we don't end up
|
||||
// in an infinite loop.
|
||||
vp9_raw_reorder_clear_slot(ctx, s);
|
||||
return AVERROR_INVALIDDATA;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
vp9_raw_reorder_clear_slot(ctx, s);
|
||||
}
|
||||
|
||||
for (s = 0; s < FRAME_SLOTS; s++) {
|
||||
if (!(frame->refresh_frame_flags & (1 << s)))
|
||||
continue;
|
||||
ctx->slot[s] = frame;
|
||||
}
|
||||
frame->slots = frame->refresh_frame_flags;
|
||||
|
||||
if (!frame->refresh_frame_flags) {
|
||||
err = vp9_raw_reorder_make_output(bsf, out, frame);
|
||||
if (err < 0) {
|
||||
av_log(bsf, AV_LOG_ERROR, "Failed to create output "
|
||||
"for transient frame.\n");
|
||||
ctx->next_frame = NULL;
|
||||
return AVERROR_INVALIDDATA;
|
||||
}
|
||||
if (!frame->needs_display) {
|
||||
vp9_raw_reorder_frame_free(&frame);
|
||||
ctx->next_frame = NULL;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
ctx->next_frame = NULL;
|
||||
return AVERROR(EAGAIN);
|
||||
|
||||
fail:
|
||||
vp9_raw_reorder_frame_free(&frame);
|
||||
return err;
|
||||
}
|
||||
|
||||
static av_cold void vp9_raw_reorder_flush_close(AVBSFContext *bsf)
|
||||
{
|
||||
VP9RawReorderContext *ctx = bsf->priv_data;
|
||||
|
||||
for (int s = 0; s < FRAME_SLOTS; s++)
|
||||
vp9_raw_reorder_clear_slot(ctx, s);
|
||||
vp9_raw_reorder_frame_free(&ctx->next_frame);
|
||||
ctx->sequence = 0;
|
||||
}
|
||||
|
||||
static const enum AVCodecID vp9_raw_reorder_codec_ids[] = {
|
||||
AV_CODEC_ID_VP9, AV_CODEC_ID_NONE,
|
||||
};
|
||||
|
||||
const FFBitStreamFilter ff_vp9_raw_reorder_bsf = {
|
||||
.p.name = "vp9_raw_reorder",
|
||||
.p.codec_ids = vp9_raw_reorder_codec_ids,
|
||||
.priv_data_size = sizeof(VP9RawReorderContext),
|
||||
.filter = &vp9_raw_reorder_filter,
|
||||
.flush = &vp9_raw_reorder_flush_close,
|
||||
.close = &vp9_raw_reorder_flush_close,
|
||||
};
|
||||
@@ -0,0 +1,231 @@
|
||||
/*
|
||||
* Vp9 invisible (alt-ref) frame to superframe merge bitstream filter
|
||||
* Copyright (c) 2016 Ronald S. Bultje <rsbultje@gmail.com>
|
||||
*
|
||||
* 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 "bsf.h"
|
||||
#include "bsf_internal.h"
|
||||
#include "get_bits.h"
|
||||
|
||||
#define MAX_CACHE 8
|
||||
typedef struct VP9BSFContext {
|
||||
int n_cache;
|
||||
AVPacket *cache[MAX_CACHE];
|
||||
} VP9BSFContext;
|
||||
|
||||
static void stats(AVPacket * const *in, int n_in,
|
||||
unsigned *_max, unsigned *_sum)
|
||||
{
|
||||
int n;
|
||||
unsigned max = 0, sum = 0;
|
||||
|
||||
for (n = 0; n < n_in; n++) {
|
||||
unsigned sz = in[n]->size;
|
||||
|
||||
if (sz > max)
|
||||
max = sz;
|
||||
sum += sz;
|
||||
}
|
||||
|
||||
*_max = max;
|
||||
*_sum = sum;
|
||||
}
|
||||
|
||||
static int merge_superframe(AVPacket * const *in, int n_in, AVPacket *out)
|
||||
{
|
||||
unsigned max, sum, mag, marker, n, sz;
|
||||
uint8_t *ptr;
|
||||
int res;
|
||||
|
||||
stats(in, n_in, &max, &sum);
|
||||
mag = av_log2(max) >> 3;
|
||||
marker = 0xC0 + (mag << 3) + (n_in - 1);
|
||||
sz = sum + 2 + (mag + 1) * n_in;
|
||||
res = av_new_packet(out, sz);
|
||||
if (res < 0)
|
||||
return res;
|
||||
ptr = out->data;
|
||||
for (n = 0; n < n_in; n++) {
|
||||
memcpy(ptr, in[n]->data, in[n]->size);
|
||||
ptr += in[n]->size;
|
||||
}
|
||||
|
||||
#define wloop(mag, wr) \
|
||||
do { \
|
||||
for (n = 0; n < n_in; n++) { \
|
||||
wr; \
|
||||
ptr += mag + 1; \
|
||||
} \
|
||||
} while (0)
|
||||
|
||||
// write superframe with marker 110[mag:2][nframes:3]
|
||||
*ptr++ = marker;
|
||||
switch (mag) {
|
||||
case 0:
|
||||
wloop(mag, *ptr = in[n]->size);
|
||||
break;
|
||||
case 1:
|
||||
wloop(mag, AV_WL16(ptr, in[n]->size));
|
||||
break;
|
||||
case 2:
|
||||
wloop(mag, AV_WL24(ptr, in[n]->size));
|
||||
break;
|
||||
case 3:
|
||||
wloop(mag, AV_WL32(ptr, in[n]->size));
|
||||
break;
|
||||
}
|
||||
*ptr++ = marker;
|
||||
av_assert0(ptr == &out->data[out->size]);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int vp9_superframe_filter(AVBSFContext *ctx, AVPacket *pkt)
|
||||
{
|
||||
GetBitContext gb;
|
||||
VP9BSFContext *s = ctx->priv_data;
|
||||
int res, invisible, profile, marker, uses_superframe_syntax = 0, n;
|
||||
|
||||
res = ff_bsf_get_packet_ref(ctx, pkt);
|
||||
if (res < 0)
|
||||
return res;
|
||||
|
||||
if (!pkt->size) {
|
||||
/* In case the cache is empty we can pass side-data-only packets
|
||||
* through unchanged. Otherwise, such a packet makes no sense. */
|
||||
if (!s->n_cache)
|
||||
return 0;
|
||||
res = AVERROR_INVALIDDATA;
|
||||
goto done;
|
||||
}
|
||||
|
||||
marker = pkt->data[pkt->size - 1];
|
||||
if ((marker & 0xe0) == 0xc0) {
|
||||
int nbytes = 1 + ((marker >> 3) & 0x3);
|
||||
int n_frames = 1 + (marker & 0x7), idx_sz = 2 + n_frames * nbytes;
|
||||
|
||||
uses_superframe_syntax = pkt->size >= idx_sz && pkt->data[pkt->size - idx_sz] == marker;
|
||||
}
|
||||
|
||||
if ((res = init_get_bits8(&gb, pkt->data, pkt->size)) < 0)
|
||||
goto done;
|
||||
|
||||
get_bits(&gb, 2); // frame marker
|
||||
profile = get_bits1(&gb);
|
||||
profile |= get_bits1(&gb) << 1;
|
||||
if (profile == 3) profile += get_bits1(&gb);
|
||||
|
||||
if (get_bits1(&gb)) {
|
||||
invisible = 0;
|
||||
} else {
|
||||
get_bits1(&gb); // keyframe
|
||||
invisible = !get_bits1(&gb);
|
||||
}
|
||||
|
||||
if (uses_superframe_syntax && s->n_cache > 0) {
|
||||
av_log(ctx, AV_LOG_ERROR,
|
||||
"Mixing of superframe syntax and naked VP9 frames not supported\n");
|
||||
res = AVERROR(ENOSYS);
|
||||
goto done;
|
||||
} else if ((!invisible || uses_superframe_syntax) && !s->n_cache) {
|
||||
// passthrough
|
||||
return 0;
|
||||
} else if (s->n_cache + 1 >= MAX_CACHE) {
|
||||
av_log(ctx, AV_LOG_ERROR,
|
||||
"Too many invisible frames\n");
|
||||
res = AVERROR_INVALIDDATA;
|
||||
goto done;
|
||||
}
|
||||
|
||||
av_packet_move_ref(s->cache[s->n_cache++], pkt);
|
||||
|
||||
if (invisible) {
|
||||
return AVERROR(EAGAIN);
|
||||
}
|
||||
av_assert0(s->n_cache > 0);
|
||||
|
||||
// build superframe
|
||||
if ((res = merge_superframe(s->cache, s->n_cache, pkt)) < 0)
|
||||
goto done;
|
||||
|
||||
res = av_packet_copy_props(pkt, s->cache[s->n_cache - 1]);
|
||||
if (res < 0)
|
||||
goto done;
|
||||
|
||||
for (n = 0; n < s->n_cache; n++)
|
||||
av_packet_unref(s->cache[n]);
|
||||
s->n_cache = 0;
|
||||
|
||||
done:
|
||||
if (res < 0)
|
||||
av_packet_unref(pkt);
|
||||
return res;
|
||||
}
|
||||
|
||||
static int vp9_superframe_init(AVBSFContext *ctx)
|
||||
{
|
||||
VP9BSFContext *s = ctx->priv_data;
|
||||
int n;
|
||||
|
||||
// alloc cache packets
|
||||
for (n = 0; n < MAX_CACHE; n++) {
|
||||
s->cache[n] = av_packet_alloc();
|
||||
if (!s->cache[n])
|
||||
return AVERROR(ENOMEM);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void vp9_superframe_flush(AVBSFContext *ctx)
|
||||
{
|
||||
VP9BSFContext *s = ctx->priv_data;
|
||||
int n;
|
||||
|
||||
// unref cached data
|
||||
for (n = 0; n < s->n_cache; n++)
|
||||
av_packet_unref(s->cache[n]);
|
||||
s->n_cache = 0;
|
||||
}
|
||||
|
||||
static void vp9_superframe_close(AVBSFContext *ctx)
|
||||
{
|
||||
VP9BSFContext *s = ctx->priv_data;
|
||||
int n;
|
||||
|
||||
// free cached data
|
||||
for (n = 0; n < MAX_CACHE; n++)
|
||||
av_packet_free(&s->cache[n]);
|
||||
}
|
||||
|
||||
static const enum AVCodecID codec_ids[] = {
|
||||
AV_CODEC_ID_VP9, AV_CODEC_ID_NONE,
|
||||
};
|
||||
|
||||
const FFBitStreamFilter ff_vp9_superframe_bsf = {
|
||||
.p.name = "vp9_superframe",
|
||||
.p.codec_ids = codec_ids,
|
||||
.priv_data_size = sizeof(VP9BSFContext),
|
||||
.filter = vp9_superframe_filter,
|
||||
.init = vp9_superframe_init,
|
||||
.flush = vp9_superframe_flush,
|
||||
.close = vp9_superframe_close,
|
||||
};
|
||||
@@ -0,0 +1,170 @@
|
||||
/*
|
||||
* 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
|
||||
*/
|
||||
|
||||
/**
|
||||
* @file
|
||||
* This bitstream filter splits VP9 superframes into packets containing
|
||||
* just one frame.
|
||||
*/
|
||||
|
||||
#include <stddef.h>
|
||||
|
||||
#include "bsf.h"
|
||||
#include "bsf_internal.h"
|
||||
#include "bytestream.h"
|
||||
#include "get_bits.h"
|
||||
|
||||
typedef struct VP9SFSplitContext {
|
||||
AVPacket *buffer_pkt;
|
||||
|
||||
int nb_frames;
|
||||
int next_frame;
|
||||
size_t next_frame_offset;
|
||||
int sizes[8];
|
||||
} VP9SFSplitContext;
|
||||
|
||||
static int vp9_superframe_split_filter(AVBSFContext *ctx, AVPacket *out)
|
||||
{
|
||||
VP9SFSplitContext *s = ctx->priv_data;
|
||||
AVPacket *in;
|
||||
int i, j, ret, marker;
|
||||
int is_superframe = !!s->buffer_pkt->data;
|
||||
|
||||
if (!s->buffer_pkt->data) {
|
||||
ret = ff_bsf_get_packet_ref(ctx, s->buffer_pkt);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
in = s->buffer_pkt;
|
||||
|
||||
if (!in->size)
|
||||
goto passthrough;
|
||||
|
||||
marker = in->data[in->size - 1];
|
||||
if ((marker & 0xe0) == 0xc0) {
|
||||
int length_size = 1 + ((marker >> 3) & 0x3);
|
||||
int nb_frames = 1 + (marker & 0x7);
|
||||
int idx_size = 2 + nb_frames * length_size;
|
||||
|
||||
if (in->size >= idx_size && in->data[in->size - idx_size] == marker) {
|
||||
GetByteContext bc;
|
||||
int64_t total_size = 0;
|
||||
|
||||
bytestream2_init(&bc, in->data + in->size + 1 - idx_size,
|
||||
nb_frames * length_size);
|
||||
|
||||
for (i = 0; i < nb_frames; i++) {
|
||||
int frame_size = 0;
|
||||
for (j = 0; j < length_size; j++)
|
||||
frame_size |= bytestream2_get_byte(&bc) << (j * 8);
|
||||
|
||||
total_size += frame_size;
|
||||
if (frame_size <= 0 || total_size > in->size - idx_size) {
|
||||
av_log(ctx, AV_LOG_ERROR,
|
||||
"Invalid frame size in a superframe: %d\n", frame_size);
|
||||
ret = AVERROR(EINVAL);
|
||||
goto fail;
|
||||
}
|
||||
s->sizes[i] = frame_size;
|
||||
}
|
||||
s->nb_frames = nb_frames;
|
||||
s->next_frame = 0;
|
||||
s->next_frame_offset = 0;
|
||||
is_superframe = 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (is_superframe) {
|
||||
GetBitContext gb;
|
||||
int profile, invisible = 0;
|
||||
|
||||
ret = av_packet_ref(out, s->buffer_pkt);
|
||||
if (ret < 0)
|
||||
goto fail;
|
||||
|
||||
out->data += s->next_frame_offset;
|
||||
out->size = s->sizes[s->next_frame];
|
||||
|
||||
s->next_frame_offset += out->size;
|
||||
s->next_frame++;
|
||||
|
||||
if (s->next_frame >= s->nb_frames)
|
||||
av_packet_unref(s->buffer_pkt);
|
||||
|
||||
ret = init_get_bits8(&gb, out->data, out->size);
|
||||
if (ret < 0)
|
||||
goto fail;
|
||||
|
||||
get_bits(&gb, 2); // frame_marker
|
||||
profile = get_bits1(&gb);
|
||||
profile |= get_bits1(&gb) << 1;
|
||||
if (profile == 3)
|
||||
get_bits1(&gb);
|
||||
if (!get_bits1(&gb)) {
|
||||
get_bits1(&gb);
|
||||
invisible = !get_bits1(&gb);
|
||||
}
|
||||
|
||||
if (invisible)
|
||||
out->pts = AV_NOPTS_VALUE;
|
||||
|
||||
} else {
|
||||
passthrough:
|
||||
av_packet_move_ref(out, s->buffer_pkt);
|
||||
}
|
||||
|
||||
return 0;
|
||||
fail:
|
||||
if (ret < 0)
|
||||
av_packet_unref(out);
|
||||
av_packet_unref(s->buffer_pkt);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int vp9_superframe_split_init(AVBSFContext *ctx)
|
||||
{
|
||||
VP9SFSplitContext *s = ctx->priv_data;
|
||||
|
||||
s->buffer_pkt = av_packet_alloc();
|
||||
if (!s->buffer_pkt)
|
||||
return AVERROR(ENOMEM);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void vp9_superframe_split_flush(AVBSFContext *ctx)
|
||||
{
|
||||
VP9SFSplitContext *s = ctx->priv_data;
|
||||
av_packet_unref(s->buffer_pkt);
|
||||
}
|
||||
|
||||
static void vp9_superframe_split_uninit(AVBSFContext *ctx)
|
||||
{
|
||||
VP9SFSplitContext *s = ctx->priv_data;
|
||||
av_packet_free(&s->buffer_pkt);
|
||||
}
|
||||
|
||||
const FFBitStreamFilter ff_vp9_superframe_split_bsf = {
|
||||
.p.name = "vp9_superframe_split",
|
||||
.p.codec_ids = (const enum AVCodecID []){ AV_CODEC_ID_VP9, AV_CODEC_ID_NONE },
|
||||
.priv_data_size = sizeof(VP9SFSplitContext),
|
||||
.init = vp9_superframe_split_init,
|
||||
.flush = vp9_superframe_split_flush,
|
||||
.close = vp9_superframe_split_uninit,
|
||||
.filter = vp9_superframe_split_filter,
|
||||
};
|
||||
@@ -0,0 +1,328 @@
|
||||
/*
|
||||
* H.266/VVC MP4 to Annex B byte stream format filter
|
||||
* Copyright (c) 2022, Thomas Siedel
|
||||
*
|
||||
* 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 <string.h>
|
||||
|
||||
#include "libavutil/intreadwrite.h"
|
||||
#include "libavutil/mem.h"
|
||||
|
||||
#include "bsf.h"
|
||||
#include "bsf_internal.h"
|
||||
#include "bytestream.h"
|
||||
#include "defs.h"
|
||||
#include "vvc.h"
|
||||
|
||||
#define MIN_VVCC_LENGTH 23
|
||||
|
||||
typedef struct VVCBSFContext {
|
||||
uint8_t length_size;
|
||||
int extradata_parsed;
|
||||
} VVCBSFContext;
|
||||
|
||||
static int vvc_extradata_to_annexb(AVBSFContext *ctx)
|
||||
{
|
||||
GetByteContext gb;
|
||||
int length_size, num_arrays, i, j;
|
||||
int ret = 0;
|
||||
int temp = 0;
|
||||
int ptl_present;
|
||||
|
||||
uint8_t *new_extradata = NULL;
|
||||
size_t new_extradata_size = 0;
|
||||
|
||||
int max_picture_width = 0;
|
||||
int max_picture_height = 0;
|
||||
int avg_frame_rate = 0;
|
||||
|
||||
bytestream2_init(&gb, ctx->par_in->extradata, ctx->par_in->extradata_size);
|
||||
temp = bytestream2_get_byte(&gb);
|
||||
length_size = ((temp & 6) >> 1) + 1;
|
||||
ptl_present = temp & 1;
|
||||
if (ptl_present) {
|
||||
int num_bytes_constraint_info;
|
||||
int general_profile_idc;
|
||||
int general_tier_flag;
|
||||
int general_level_idc;
|
||||
int ptl_frame_only_constraint_flag;
|
||||
int ptl_multi_layer_enabled_flag;
|
||||
int ptl_num_sub_profiles;
|
||||
int temp3, temp4, temp5;
|
||||
int temp2 = bytestream2_get_be16(&gb);
|
||||
int ols_idx = (temp2 >> 7) & 0x1ff;
|
||||
int num_sublayers = (temp2 >> 4) & 0x7;
|
||||
int constant_frame_rate = (temp2 >> 2) & 0x3;
|
||||
int chroma_format_idc = temp2 & 0x3;
|
||||
int bit_depth_minus8 = (bytestream2_get_byte(&gb) >> 5) & 0x7;
|
||||
av_log(ctx, AV_LOG_DEBUG,
|
||||
"bit_depth_minus8 %d chroma_format_idc %d\n", bit_depth_minus8,
|
||||
chroma_format_idc);
|
||||
av_log(ctx, AV_LOG_DEBUG, "constant_frame_rate %d, ols_idx %d\n",
|
||||
constant_frame_rate, ols_idx);
|
||||
// VvcPTLRecord(num_sublayers) native_ptl
|
||||
temp3 = bytestream2_get_byte(&gb);
|
||||
num_bytes_constraint_info = (temp3) & 0x3f;
|
||||
temp4 = bytestream2_get_byte(&gb);
|
||||
general_profile_idc = (temp4 >> 1) & 0x7f;
|
||||
general_tier_flag = (temp4) & 1;
|
||||
general_level_idc = bytestream2_get_byte(&gb);
|
||||
av_log(ctx, AV_LOG_DEBUG,
|
||||
"general_profile_idc %d, general_tier_flag %d, general_level_idc %d, num_sublayers %d num_bytes_constraint_info %d\n",
|
||||
general_profile_idc, general_tier_flag, general_level_idc,
|
||||
num_sublayers, num_bytes_constraint_info);
|
||||
|
||||
temp5 = bytestream2_get_byte(&gb);
|
||||
ptl_frame_only_constraint_flag = (temp5 >> 7) & 0x1;
|
||||
ptl_multi_layer_enabled_flag = (temp5 >> 6) & 0x1;
|
||||
for (i = 0; i < num_bytes_constraint_info - 1; i++) {
|
||||
// unsigned int(8*num_bytes_constraint_info - 2) general_constraint_info;
|
||||
bytestream2_get_byte(&gb);
|
||||
}
|
||||
|
||||
av_log(ctx, AV_LOG_DEBUG,
|
||||
"ptl_multi_layer_enabled_flag %d, ptl_frame_only_constraint_flag %d\n",
|
||||
ptl_multi_layer_enabled_flag, ptl_frame_only_constraint_flag);
|
||||
|
||||
if (num_sublayers > 1) {
|
||||
int temp6 = bytestream2_get_byte(&gb);
|
||||
uint8_t ptl_sublayer_level_present_flag[8] = { 0 };
|
||||
//uint8_t sublayer_level_idc[8] = {0};
|
||||
for (i = num_sublayers - 2; i >= 0; i--) {
|
||||
ptl_sublayer_level_present_flag[i] =
|
||||
(temp6 >> (7 - (num_sublayers - 2 - i))) & 0x01;
|
||||
}
|
||||
// for (j=num_sublayers; j<=8 && num_sublayers > 1; j++)
|
||||
// bit(1) ptl_reserved_zero_bit = 0;
|
||||
for (i = num_sublayers - 2; i >= 0; i--) {
|
||||
if (ptl_sublayer_level_present_flag[i]) {
|
||||
//sublayer_level_idc[i] = bytestream2_get_byte(&gb);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ptl_num_sub_profiles = bytestream2_get_byte(&gb);
|
||||
for (j = 0; j < ptl_num_sub_profiles; j++) {
|
||||
// unsigned int(32) general_sub_profile_idc[j];
|
||||
bytestream2_get_be16(&gb);
|
||||
bytestream2_get_be16(&gb);
|
||||
}
|
||||
|
||||
max_picture_width = bytestream2_get_be16(&gb); // unsigned_int(16) max_picture_width;
|
||||
max_picture_height = bytestream2_get_be16(&gb); // unsigned_int(16) max_picture_height;
|
||||
avg_frame_rate = bytestream2_get_be16(&gb); // unsigned int(16) avg_frame_rate; }
|
||||
av_log(ctx, AV_LOG_DEBUG,
|
||||
"max_picture_width %d, max_picture_height %d, avg_frame_rate %d\n",
|
||||
max_picture_width, max_picture_height, avg_frame_rate);
|
||||
}
|
||||
|
||||
num_arrays = bytestream2_get_byte(&gb);
|
||||
|
||||
for (i = 0; i < num_arrays; i++) {
|
||||
int cnt;
|
||||
int type = bytestream2_get_byte(&gb) & 0x1f;
|
||||
|
||||
if (type == VVC_OPI_NUT || type == VVC_DCI_NUT)
|
||||
cnt = 1;
|
||||
else
|
||||
cnt = bytestream2_get_be16(&gb);
|
||||
|
||||
av_log(ctx, AV_LOG_DEBUG, "nalu_type %d cnt %d\n", type, cnt);
|
||||
|
||||
if (!(type == VVC_OPI_NUT || type == VVC_DCI_NUT ||
|
||||
type == VVC_VPS_NUT || type == VVC_SPS_NUT || type == VVC_PPS_NUT
|
||||
|| type == VVC_PREFIX_SEI_NUT || type == VVC_SUFFIX_SEI_NUT)) {
|
||||
av_log(ctx, AV_LOG_ERROR,
|
||||
"Invalid NAL unit type in extradata: %d\n", type);
|
||||
ret = AVERROR_INVALIDDATA;
|
||||
goto fail;
|
||||
}
|
||||
|
||||
for (j = 0; j < cnt; j++) {
|
||||
const int nalu_len = bytestream2_get_be16(&gb);
|
||||
|
||||
if (!nalu_len ||
|
||||
nalu_len > bytestream2_get_bytes_left(&gb) ||
|
||||
4 + AV_INPUT_BUFFER_PADDING_SIZE + nalu_len > SIZE_MAX - new_extradata_size) {
|
||||
ret = AVERROR_INVALIDDATA;
|
||||
goto fail;
|
||||
}
|
||||
ret = av_reallocp(&new_extradata, new_extradata_size + nalu_len + 4
|
||||
+ AV_INPUT_BUFFER_PADDING_SIZE);
|
||||
if (ret < 0)
|
||||
goto fail;
|
||||
|
||||
AV_WB32(new_extradata + new_extradata_size, 1); // add the startcode
|
||||
bytestream2_get_buffer(&gb, new_extradata + new_extradata_size + 4,
|
||||
nalu_len);
|
||||
new_extradata_size += 4 + nalu_len;
|
||||
memset(new_extradata + new_extradata_size, 0,
|
||||
AV_INPUT_BUFFER_PADDING_SIZE);
|
||||
}
|
||||
}
|
||||
|
||||
av_freep(&ctx->par_out->extradata);
|
||||
ctx->par_out->extradata = new_extradata;
|
||||
ctx->par_out->extradata_size = new_extradata_size;
|
||||
|
||||
if (!new_extradata_size)
|
||||
av_log(ctx, AV_LOG_WARNING, "No parameter sets in the extradata\n");
|
||||
|
||||
return length_size;
|
||||
fail:
|
||||
av_freep(&new_extradata);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int vvc_mp4toannexb_init(AVBSFContext *ctx)
|
||||
{
|
||||
VVCBSFContext *s = ctx->priv_data;
|
||||
int ret;
|
||||
|
||||
if (ctx->par_in->extradata_size < MIN_VVCC_LENGTH ||
|
||||
AV_RB24(ctx->par_in->extradata) == 1 ||
|
||||
AV_RB32(ctx->par_in->extradata) == 1) {
|
||||
av_log(ctx, AV_LOG_VERBOSE,
|
||||
"The input looks like it is Annex B already\n");
|
||||
} else {
|
||||
ret = vvc_extradata_to_annexb(ctx);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
s->length_size = ret;
|
||||
s->extradata_parsed = 1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int vvc_mp4toannexb_filter(AVBSFContext *ctx, AVPacket *out)
|
||||
{
|
||||
VVCBSFContext *s = ctx->priv_data;
|
||||
AVPacket *in;
|
||||
GetByteContext gb;
|
||||
|
||||
int is_irap = 0;
|
||||
int added_extra = 0;
|
||||
int i, ret = 0;
|
||||
|
||||
ret = ff_bsf_get_packet(ctx, &in);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
if (!s->extradata_parsed) {
|
||||
av_packet_move_ref(out, in);
|
||||
av_packet_free(&in);
|
||||
return 0;
|
||||
}
|
||||
|
||||
bytestream2_init(&gb, in->data, in->size);
|
||||
|
||||
/* check if this packet contains an IRAP. The extradata will need to be added before any potential PH_NUT */
|
||||
while (bytestream2_get_bytes_left(&gb)) {
|
||||
uint32_t nalu_size = 0;
|
||||
int nalu_type;
|
||||
|
||||
if (bytestream2_get_bytes_left(&gb) < s->length_size) {
|
||||
ret = AVERROR_INVALIDDATA;
|
||||
goto fail;
|
||||
}
|
||||
|
||||
for (i = 0; i < s->length_size; i++)
|
||||
nalu_size = (nalu_size << 8) | bytestream2_get_byte(&gb);
|
||||
|
||||
if (nalu_size < 2 || nalu_size > bytestream2_get_bytes_left(&gb)) {
|
||||
ret = AVERROR_INVALIDDATA;
|
||||
goto fail;
|
||||
}
|
||||
|
||||
nalu_type = (bytestream2_peek_be16(&gb) >> 3) & 0x1f;
|
||||
is_irap = nalu_type >= VVC_IDR_W_RADL && nalu_type <= VVC_RSV_IRAP_11;
|
||||
if (is_irap) {
|
||||
break;
|
||||
}
|
||||
bytestream2_seek(&gb, nalu_size, SEEK_CUR);
|
||||
}
|
||||
|
||||
bytestream2_seek(&gb, 0, SEEK_SET);
|
||||
while (bytestream2_get_bytes_left(&gb)) {
|
||||
uint32_t nalu_size = 0;
|
||||
int nalu_type;
|
||||
int add_extradata, extra_size, prev_size;
|
||||
|
||||
if (bytestream2_get_bytes_left(&gb) < s->length_size) {
|
||||
ret = AVERROR_INVALIDDATA;
|
||||
goto fail;
|
||||
}
|
||||
|
||||
for (i = 0; i < s->length_size; i++)
|
||||
nalu_size = (nalu_size << 8) | bytestream2_get_byte(&gb);
|
||||
|
||||
if (nalu_size < 2 || nalu_size > bytestream2_get_bytes_left(&gb)) {
|
||||
ret = AVERROR_INVALIDDATA;
|
||||
goto fail;
|
||||
}
|
||||
|
||||
nalu_type = (bytestream2_peek_be16(&gb) >> 3) & 0x1f;
|
||||
|
||||
/* prepend extradata to IRAP frames */
|
||||
add_extradata = is_irap && nalu_type != VVC_AUD_NUT && !added_extra;
|
||||
extra_size = add_extradata * ctx->par_out->extradata_size;
|
||||
added_extra |= add_extradata;
|
||||
|
||||
if (FFMIN(INT_MAX, SIZE_MAX) < 4ULL + nalu_size + extra_size) {
|
||||
ret = AVERROR_INVALIDDATA;
|
||||
goto fail;
|
||||
}
|
||||
|
||||
prev_size = out->size;
|
||||
|
||||
ret = av_grow_packet(out, 4 + nalu_size + extra_size);
|
||||
if (ret < 0)
|
||||
goto fail;
|
||||
|
||||
if (extra_size)
|
||||
memcpy(out->data + prev_size, ctx->par_out->extradata, extra_size);
|
||||
AV_WB32(out->data + prev_size + extra_size, 1);
|
||||
bytestream2_get_buffer(&gb, out->data + prev_size + 4 + extra_size,
|
||||
nalu_size);
|
||||
}
|
||||
|
||||
ret = av_packet_copy_props(out, in);
|
||||
if (ret < 0)
|
||||
goto fail;
|
||||
|
||||
fail:
|
||||
if (ret < 0)
|
||||
av_packet_unref(out);
|
||||
av_packet_free(&in);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static const enum AVCodecID codec_ids[] = {
|
||||
AV_CODEC_ID_VVC, AV_CODEC_ID_NONE,
|
||||
};
|
||||
|
||||
const FFBitStreamFilter ff_vvc_mp4toannexb_bsf = {
|
||||
.p.name = "vvc_mp4toannexb",
|
||||
.p.codec_ids = codec_ids,
|
||||
.priv_data_size = sizeof(VVCBSFContext),
|
||||
.init = vvc_mp4toannexb_init,
|
||||
.filter = vvc_mp4toannexb_filter,
|
||||
};
|
||||
Reference in New Issue
Block a user