avcodec: add unpack packed B-frames bitstream filter

Fixes Ticket #2913

Signed-off-by: Andreas Cadhalpun <Andreas.Cadhalpun@googlemail.com>
Signed-off-by: Michael Niedermayer <michaelni@gmx.at>
This commit is contained in:
Andreas Cadhalpun 2015-04-03 18:19:53 +02:00 committed by Michael Niedermayer
parent 26f2e2f3f7
commit 0224b35c89
6 changed files with 218 additions and 1 deletions

View File

@ -13,6 +13,7 @@ version <next>:
- Intel QSV-accelerated H.264 encoding
- MMAL-accelerated H.264 decoding
- basic APNG encoder and muxer
- unpack DivX-style packed B-frames in MPEG-4 bitstream filter
version 2.6:

View File

@ -139,6 +139,26 @@ ffmpeg -i frame_%d.jpg -c:v copy rotated.avi
@section mp3_header_decompress
@section mpeg4_unpack_bframes
Unpack DivX-style packed B-frames.
DivX-style packed B-frames are not valid MPEG-4 and were only a
workaround for the broken Video for Windows subsystem.
They use more space, can cause minor AV sync issues, require more
CPU power to decode (unless the player has some decoded picture queue
to compensate the 2,0,2,0 frame per packet style) and cause
trouble if copied into a standard container like mp4 or mpeg-ps/ts,
because MPEG-4 decoders may not be able to decode them, since they are
not valid MPEG-4.
For example to fix an AVI file containing an MPEG-4 stream with
DivX-style packed B-frames using @command{ffmpeg}, you can use the command:
@example
ffmpeg -i INPUT.avi -codec copy -bsf:v mpeg4_unpack_bframes OUTPUT.avi
@end example
@section noise
Damages the contents of packets without damaging the container. Can be

View File

@ -841,6 +841,7 @@ OBJS-$(CONFIG_H264_MP4TOANNEXB_BSF) += h264_mp4toannexb_bsf.o
OBJS-$(CONFIG_IMX_DUMP_HEADER_BSF) += imx_dump_header_bsf.o
OBJS-$(CONFIG_MJPEG2JPEG_BSF) += mjpeg2jpeg_bsf.o
OBJS-$(CONFIG_MJPEGA_DUMP_HEADER_BSF) += mjpega_dump_header_bsf.o
OBJS-$(CONFIG_MPEG4_UNPACK_BFRAMES_BSF) += mpeg4_unpack_bframes_bsf.o
OBJS-$(CONFIG_MOV2TEXTSUB_BSF) += movsub_bsf.o
OBJS-$(CONFIG_MP3_HEADER_DECOMPRESS_BSF) += mp3_header_decompress_bsf.o \
mpegaudiodata.o

View File

@ -605,6 +605,7 @@ void avcodec_register_all(void)
REGISTER_BSF(MJPEG2JPEG, mjpeg2jpeg);
REGISTER_BSF(MJPEGA_DUMP_HEADER, mjpega_dump_header);
REGISTER_BSF(MP3_HEADER_DECOMPRESS, mp3_header_decompress);
REGISTER_BSF(MPEG4_UNPACK_BFRAMES, mpeg4_unpack_bframes);
REGISTER_BSF(MOV2TEXTSUB, mov2textsub);
REGISTER_BSF(NOISE, noise);
REGISTER_BSF(REMOVE_EXTRADATA, remove_extradata);

View File

@ -0,0 +1,194 @@
/*
* 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 "avcodec.h"
#include "mpeg4video.h"
typedef struct UnpackBFramesBSFContext {
uint8_t *b_frame_buf;
int b_frame_buf_size;
int updated_extradata;
} UnpackBFramesBSFContext;
/* search next start code */
static unsigned int find_startcode(const uint8_t *buf, int buf_size, int *pos)
{
unsigned int startcode = 0xFF;
for (; *pos < buf_size;) {
startcode = ((startcode << 8) | buf[*pos]) & 0xFFFFFFFF;
*pos +=1;
if ((startcode & 0xFFFFFF00) != 0x100)
continue; /* no startcode */
return startcode;
}
return 0;
}
/* 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) {
unsigned int startcode;
int pos, i;
for (pos = 0; pos < buf_size;) {
startcode = find_startcode(buf, buf_size, &pos);
if (startcode == USER_DATA_STARTCODE && pos_p) {
/* check if the (DivX) userdata string ends with 'p' (packed) */
for (i = 0; i < 255 && pos + i + 1 < buf_size; i++) {
if (buf[pos + i] == 'p' && buf[pos + i + 1] == '\0') {
*pos_p = pos + i;
break;
}
}
} else if (startcode == VOP_STARTCODE && nb_vop) {
*nb_vop += 1;
if (*nb_vop == 2 && pos_vop2) {
*pos_vop2 = pos - 4; /* subtract 4 bytes startcode */
}
}
}
}
/* allocate new buffer and copy size bytes from src */
static uint8_t *create_new_buffer(const uint8_t *src, int size) {
uint8_t *dst = av_malloc(size + FF_INPUT_BUFFER_PADDING_SIZE);
if (dst) {
memcpy(dst, src, size);
memset(dst + size, 0, FF_INPUT_BUFFER_PADDING_SIZE);
}
return dst;
}
static int mpeg4_unpack_bframes_filter(AVBitStreamFilterContext *bsfc,
AVCodecContext *avctx, const char *args,
uint8_t **poutbuf, int *poutbuf_size,
const uint8_t *buf, int buf_size,
int keyframe)
{
UnpackBFramesBSFContext *ctx = bsfc->priv_data;
int pos_p = -1, nb_vop = 0, pos_vop2 = -1, ret = 0;
if (avctx->codec_id != AV_CODEC_ID_MPEG4) {
av_log(avctx, AV_LOG_ERROR,
"The mpeg4_unpack_bframes bitstream filter is only useful for mpeg4.\n");
return AVERROR(EINVAL);
}
if (!ctx->updated_extradata && avctx->extradata) {
int pos_p_ext = -1;
scan_buffer(avctx->extradata, avctx->extradata_size, &pos_p_ext, NULL, NULL);
if (pos_p_ext >= 0) {
av_log(avctx, AV_LOG_DEBUG,
"Updating DivX userdata (remove trailing 'p') in extradata.\n");
avctx->extradata[pos_p_ext] = '\0';
}
ctx->updated_extradata = 1;
}
scan_buffer(buf, buf_size, &pos_p, &nb_vop, &pos_vop2);
av_log(avctx, AV_LOG_DEBUG, "Found %d VOP startcode(s) in this packet.\n", nb_vop);
if (pos_vop2 >= 0) {
if (ctx->b_frame_buf) {
av_log(avctx, AV_LOG_WARNING,
"Missing one N-VOP packet, discarding one B-frame.\n");
av_freep(&ctx->b_frame_buf);
ctx->b_frame_buf_size = 0;
}
/* store the packed B-frame in the BSFContext */
ctx->b_frame_buf_size = buf_size - pos_vop2;
ctx->b_frame_buf = create_new_buffer(buf + pos_vop2, ctx->b_frame_buf_size);
if (!ctx->b_frame_buf) {
ctx->b_frame_buf_size = 0;
return AVERROR(ENOMEM);
}
}
if (nb_vop > 2) {
av_log(avctx, AV_LOG_WARNING,
"Found %d VOP headers in one packet, only unpacking one.\n", nb_vop);
}
if (nb_vop == 1 && ctx->b_frame_buf) {
/* use frame from BSFContext */
*poutbuf = ctx->b_frame_buf;
*poutbuf_size = ctx->b_frame_buf_size;
/* the output buffer is distinct from the input buffer */
ret = 1;
if (buf_size <= MAX_NVOP_SIZE) {
/* N-VOP */
av_log(avctx, AV_LOG_DEBUG, "Skipping N-VOP.\n");
ctx->b_frame_buf = NULL;
ctx->b_frame_buf_size = 0;
} else {
/* copy packet into BSFContext */
ctx->b_frame_buf_size = buf_size;
ctx->b_frame_buf = create_new_buffer(buf , buf_size);
if (!ctx->b_frame_buf) {
ctx->b_frame_buf_size = 0;
av_freep(poutbuf);
*poutbuf_size = 0;
return AVERROR(ENOMEM);
}
}
} else if (nb_vop >= 2) {
/* use first frame of the packet */
*poutbuf = (uint8_t *) buf;
*poutbuf_size = pos_vop2;
} else if (pos_p >= 0) {
av_log(avctx, AV_LOG_DEBUG, "Updating DivX userdata (remove trailing 'p').\n");
*poutbuf_size = buf_size;
*poutbuf = create_new_buffer(buf, buf_size);
if (!*poutbuf) {
*poutbuf_size = 0;
return AVERROR(ENOMEM);
}
/* remove 'p' (packed) from the end of the (DivX) userdata string */
(*poutbuf)[pos_p] = '\0';
/* the output buffer is distinct from the input buffer */
ret = 1;
} else {
/* copy packet */
*poutbuf = (uint8_t *) buf;
*poutbuf_size = buf_size;
}
return ret;
}
static void mpeg4_unpack_bframes_close(AVBitStreamFilterContext *bsfc)
{
UnpackBFramesBSFContext *ctx = bsfc->priv_data;
av_freep(&ctx->b_frame_buf);
}
AVBitStreamFilter ff_mpeg4_unpack_bframes_bsf = {
.name = "mpeg4_unpack_bframes",
.priv_data_size = sizeof(UnpackBFramesBSFContext),
.filter = mpeg4_unpack_bframes_filter,
.close = mpeg4_unpack_bframes_close
};

View File

@ -29,7 +29,7 @@
#include "libavutil/version.h"
#define LIBAVCODEC_VERSION_MAJOR 56
#define LIBAVCODEC_VERSION_MINOR 33
#define LIBAVCODEC_VERSION_MINOR 34
#define LIBAVCODEC_VERSION_MICRO 100
#define LIBAVCODEC_VERSION_INT AV_VERSION_INT(LIBAVCODEC_VERSION_MAJOR, \