]> git.sesse.net Git - ffmpeg/blob - libavcodec/mpeg4_unpack_bframes_bsf.c
Merge commit '7e42d5f0ab2aeac811fd01e122627c9198b13f01'
[ffmpeg] / libavcodec / mpeg4_unpack_bframes_bsf.c
1 /*
2  * Bitstream filter for unpacking DivX-style packed B-frames in MPEG-4 (divx_packed)
3  * Copyright (c) 2015 Andreas Cadhalpun <Andreas.Cadhalpun@googlemail.com>
4  *
5  * This file is part of FFmpeg.
6  *
7  * FFmpeg is free software; you can redistribute it and/or
8  * modify it under the terms of the GNU Lesser General Public
9  * License as published by the Free Software Foundation; either
10  * version 2.1 of the License, or (at your option) any later version.
11  *
12  * FFmpeg is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
15  * Lesser General Public License for more details.
16  *
17  * You should have received a copy of the GNU Lesser General Public
18  * License along with FFmpeg; if not, write to the Free Software
19  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
20  */
21
22 #include "avcodec.h"
23 #include "bsf.h"
24 #include "internal.h"
25 #include "mpeg4video.h"
26
27 typedef struct UnpackBFramesBSFContext {
28     AVPacket *b_frame;
29 } UnpackBFramesBSFContext;
30
31 /* determine the position of the packed marker in the userdata,
32  * the number of VOPs and the position of the second VOP */
33 static void scan_buffer(const uint8_t *buf, int buf_size,
34                         int *pos_p, int *nb_vop, int *pos_vop2) {
35     uint32_t startcode;
36     const uint8_t *end = buf + buf_size, *pos = buf;
37
38     while (pos < end) {
39         startcode = -1;
40         pos = avpriv_find_start_code(pos, end, &startcode);
41
42         if (startcode == USER_DATA_STARTCODE && pos_p) {
43             /* check if the (DivX) userdata string ends with 'p' (packed) */
44             for (int i = 0; i < 255 && pos + i + 1 < end; i++) {
45                 if (pos[i] == 'p' && pos[i + 1] == '\0') {
46                     *pos_p = pos + i - buf;
47                     break;
48                 }
49             }
50         } else if (startcode == VOP_STARTCODE && nb_vop) {
51             *nb_vop += 1;
52             if (*nb_vop == 2 && pos_vop2) {
53                 *pos_vop2 = pos - buf - 4; /* subtract 4 bytes startcode */
54             }
55         }
56     }
57 }
58
59 static int mpeg4_unpack_bframes_filter(AVBSFContext *ctx, AVPacket *out)
60 {
61     UnpackBFramesBSFContext *s = ctx->priv_data;
62     int pos_p = -1, nb_vop = 0, pos_vop2 = -1, ret = 0;
63     AVPacket *in;
64
65     ret = ff_bsf_get_packet(ctx, &in);
66     if (ret < 0)
67         return ret;
68
69     scan_buffer(in->data, in->size, &pos_p, &nb_vop, &pos_vop2);
70     av_log(ctx, AV_LOG_DEBUG, "Found %d VOP startcode(s) in this packet.\n", nb_vop);
71
72     if (pos_vop2 >= 0) {
73         if (s->b_frame->data) {
74             av_log(ctx, AV_LOG_WARNING,
75                    "Missing one N-VOP packet, discarding one B-frame.\n");
76             av_packet_unref(s->b_frame);
77         }
78         /* store the packed B-frame in the BSFContext */
79         ret = av_packet_ref(s->b_frame, in);
80         if (ret < 0) {
81             goto fail;
82         }
83         s->b_frame->size -= pos_vop2;
84         s->b_frame->data += pos_vop2;
85     }
86
87     if (nb_vop > 2) {
88         av_log(ctx, AV_LOG_WARNING,
89        "Found %d VOP headers in one packet, only unpacking one.\n", nb_vop);
90     }
91
92     if (nb_vop == 1 && s->b_frame->data) {
93         /* use frame from BSFContext */
94         av_packet_move_ref(out, s->b_frame);
95
96         /* use properties from current input packet */
97         ret = av_packet_copy_props(out, in);
98         if (ret < 0) {
99             goto fail;
100         }
101
102         if (in->size <= MAX_NVOP_SIZE) {
103             /* N-VOP */
104             av_log(ctx, AV_LOG_DEBUG, "Skipping N-VOP.\n");
105         } else {
106             /* copy packet into BSFContext */
107             av_packet_move_ref(s->b_frame, in);
108         }
109     } else if (nb_vop >= 2) {
110         /* use first frame of the packet */
111         av_packet_move_ref(out, in);
112         out->size = pos_vop2;
113     } else if (pos_p >= 0) {
114         ret = av_packet_make_writable(in);
115         if (ret < 0)
116             goto fail;
117         av_log(ctx, AV_LOG_DEBUG, "Updating DivX userdata (remove trailing 'p').\n");
118         av_packet_move_ref(out, in);
119         /* remove 'p' (packed) from the end of the (DivX) userdata string */
120         out->data[pos_p] = '\0';
121     } else {
122         /* copy packet */
123         av_packet_move_ref(out, in);
124     }
125
126 fail:
127     if (ret < 0)
128         av_packet_unref(out);
129     av_packet_free(&in);
130
131     return ret;
132 }
133
134 static int mpeg4_unpack_bframes_init(AVBSFContext *ctx)
135 {
136     UnpackBFramesBSFContext *s = ctx->priv_data;
137
138     s->b_frame = av_packet_alloc();
139     if (!s->b_frame)
140         return AVERROR(ENOMEM);
141
142     if (ctx->par_in->extradata) {
143         int pos_p_ext = -1;
144         scan_buffer(ctx->par_in->extradata, ctx->par_in->extradata_size, &pos_p_ext, NULL, NULL);
145         if (pos_p_ext >= 0) {
146             av_log(ctx, AV_LOG_DEBUG,
147                    "Updating DivX userdata (remove trailing 'p') in extradata.\n");
148             ctx->par_out->extradata[pos_p_ext] = '\0';
149         }
150     }
151
152     return 0;
153 }
154
155 static void mpeg4_unpack_bframes_flush(AVBSFContext *bsfc)
156 {
157     UnpackBFramesBSFContext *ctx = bsfc->priv_data;
158     av_packet_unref(ctx->b_frame);
159 }
160
161 static void mpeg4_unpack_bframes_close(AVBSFContext *bsfc)
162 {
163     UnpackBFramesBSFContext *ctx = bsfc->priv_data;
164     av_packet_free(&ctx->b_frame);
165 }
166
167 static const enum AVCodecID codec_ids[] = {
168     AV_CODEC_ID_MPEG4, AV_CODEC_ID_NONE,
169 };
170
171 const AVBitStreamFilter ff_mpeg4_unpack_bframes_bsf = {
172     .name           = "mpeg4_unpack_bframes",
173     .priv_data_size = sizeof(UnpackBFramesBSFContext),
174     .init           = mpeg4_unpack_bframes_init,
175     .filter         = mpeg4_unpack_bframes_filter,
176     .flush          = mpeg4_unpack_bframes_flush,
177     .close          = mpeg4_unpack_bframes_close,
178     .codec_ids      = codec_ids,
179 };