blob: e227f58ec6df6b314b556757965743166490bf2b
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 "mpeg4video.h" |
25 | |
26 | typedef struct UnpackBFramesBSFContext { |
27 | uint8_t *b_frame_buf; |
28 | int b_frame_buf_size; |
29 | } UnpackBFramesBSFContext; |
30 | |
31 | /* search next start code */ |
32 | static unsigned int find_startcode(const uint8_t *buf, int buf_size, int *pos) |
33 | { |
34 | unsigned int startcode = 0xFF; |
35 | |
36 | for (; *pos < buf_size;) { |
37 | startcode = ((startcode << 8) | buf[*pos]) & 0xFFFFFFFF; |
38 | *pos +=1; |
39 | if ((startcode & 0xFFFFFF00) != 0x100) |
40 | continue; /* no startcode */ |
41 | return startcode; |
42 | } |
43 | |
44 | return 0; |
45 | } |
46 | |
47 | /* determine the position of the packed marker in the userdata, |
48 | * the number of VOPs and the position of the second VOP */ |
49 | static void scan_buffer(const uint8_t *buf, int buf_size, |
50 | int *pos_p, int *nb_vop, int *pos_vop2) { |
51 | unsigned int startcode; |
52 | int pos, i; |
53 | |
54 | for (pos = 0; pos < buf_size;) { |
55 | startcode = find_startcode(buf, buf_size, &pos); |
56 | |
57 | if (startcode == USER_DATA_STARTCODE && pos_p) { |
58 | /* check if the (DivX) userdata string ends with 'p' (packed) */ |
59 | for (i = 0; i < 255 && pos + i + 1 < buf_size; i++) { |
60 | if (buf[pos + i] == 'p' && buf[pos + i + 1] == '\0') { |
61 | *pos_p = pos + i; |
62 | break; |
63 | } |
64 | } |
65 | } else if (startcode == VOP_STARTCODE && nb_vop) { |
66 | *nb_vop += 1; |
67 | if (*nb_vop == 2 && pos_vop2) { |
68 | *pos_vop2 = pos - 4; /* subtract 4 bytes startcode */ |
69 | } |
70 | } |
71 | } |
72 | } |
73 | |
74 | /* allocate new buffer and copy size bytes from src */ |
75 | static uint8_t *create_new_buffer(const uint8_t *src, int size) { |
76 | uint8_t *dst = av_malloc(size + AV_INPUT_BUFFER_PADDING_SIZE); |
77 | |
78 | if (dst) { |
79 | memcpy(dst, src, size); |
80 | memset(dst + size, 0, AV_INPUT_BUFFER_PADDING_SIZE); |
81 | } |
82 | |
83 | return dst; |
84 | } |
85 | |
86 | static int mpeg4_unpack_bframes_filter(AVBSFContext *ctx, AVPacket *out) |
87 | { |
88 | UnpackBFramesBSFContext *s = ctx->priv_data; |
89 | int pos_p = -1, nb_vop = 0, pos_vop2 = -1, ret = 0; |
90 | AVPacket *in; |
91 | |
92 | ret = ff_bsf_get_packet(ctx, &in); |
93 | if (ret < 0) |
94 | return ret; |
95 | |
96 | scan_buffer(in->data, in->size, &pos_p, &nb_vop, &pos_vop2); |
97 | av_log(ctx, AV_LOG_DEBUG, "Found %d VOP startcode(s) in this packet.\n", nb_vop); |
98 | |
99 | if (pos_vop2 >= 0) { |
100 | if (s->b_frame_buf) { |
101 | av_log(ctx, AV_LOG_WARNING, |
102 | "Missing one N-VOP packet, discarding one B-frame.\n"); |
103 | av_freep(&s->b_frame_buf); |
104 | s->b_frame_buf_size = 0; |
105 | } |
106 | /* store the packed B-frame in the BSFContext */ |
107 | s->b_frame_buf_size = in->size - pos_vop2; |
108 | s->b_frame_buf = create_new_buffer(in->data + pos_vop2, s->b_frame_buf_size); |
109 | if (!s->b_frame_buf) { |
110 | s->b_frame_buf_size = 0; |
111 | av_packet_free(&in); |
112 | return AVERROR(ENOMEM); |
113 | } |
114 | } |
115 | |
116 | if (nb_vop > 2) { |
117 | av_log(ctx, AV_LOG_WARNING, |
118 | "Found %d VOP headers in one packet, only unpacking one.\n", nb_vop); |
119 | } |
120 | |
121 | if (nb_vop == 1 && s->b_frame_buf) { |
122 | /* use frame from BSFContext */ |
123 | ret = av_packet_copy_props(out, in); |
124 | if (ret < 0) { |
125 | av_packet_free(&in); |
126 | return ret; |
127 | } |
128 | |
129 | ret = av_packet_from_data(out, s->b_frame_buf, s->b_frame_buf_size); |
130 | if (ret < 0) { |
131 | av_packet_free(&in); |
132 | return ret; |
133 | } |
134 | if (in->size <= MAX_NVOP_SIZE) { |
135 | /* N-VOP */ |
136 | av_log(ctx, AV_LOG_DEBUG, "Skipping N-VOP.\n"); |
137 | s->b_frame_buf = NULL; |
138 | s->b_frame_buf_size = 0; |
139 | } else { |
140 | /* copy packet into BSFContext */ |
141 | s->b_frame_buf_size = in->size; |
142 | s->b_frame_buf = create_new_buffer(in->data, in->size); |
143 | if (!s->b_frame_buf) { |
144 | s->b_frame_buf_size = 0; |
145 | av_packet_unref(out); |
146 | av_packet_free(&in); |
147 | return AVERROR(ENOMEM); |
148 | } |
149 | } |
150 | } else if (nb_vop >= 2) { |
151 | /* use first frame of the packet */ |
152 | av_packet_move_ref(out, in); |
153 | out->size = pos_vop2; |
154 | } else if (pos_p >= 0) { |
155 | av_log(ctx, AV_LOG_DEBUG, "Updating DivX userdata (remove trailing 'p').\n"); |
156 | av_packet_move_ref(out, in); |
157 | /* remove 'p' (packed) from the end of the (DivX) userdata string */ |
158 | out->data[pos_p] = '\0'; |
159 | } else { |
160 | /* copy packet */ |
161 | av_packet_move_ref(out, in); |
162 | } |
163 | |
164 | av_packet_free(&in); |
165 | |
166 | return 0; |
167 | } |
168 | |
169 | static int mpeg4_unpack_bframes_init(AVBSFContext *ctx) |
170 | { |
171 | if (ctx->par_in->extradata) { |
172 | int pos_p_ext = -1; |
173 | scan_buffer(ctx->par_in->extradata, ctx->par_in->extradata_size, &pos_p_ext, NULL, NULL); |
174 | if (pos_p_ext >= 0) { |
175 | av_log(ctx, AV_LOG_DEBUG, |
176 | "Updating DivX userdata (remove trailing 'p') in extradata.\n"); |
177 | ctx->par_out->extradata[pos_p_ext] = '\0'; |
178 | } |
179 | } |
180 | |
181 | return 0; |
182 | } |
183 | |
184 | static void mpeg4_unpack_bframes_close(AVBSFContext *bsfc) |
185 | { |
186 | UnpackBFramesBSFContext *ctx = bsfc->priv_data; |
187 | av_freep(&ctx->b_frame_buf); |
188 | } |
189 | |
190 | static const enum AVCodecID codec_ids[] = { |
191 | AV_CODEC_ID_MPEG4, AV_CODEC_ID_NONE, |
192 | }; |
193 | |
194 | const AVBitStreamFilter ff_mpeg4_unpack_bframes_bsf = { |
195 | .name = "mpeg4_unpack_bframes", |
196 | .priv_data_size = sizeof(UnpackBFramesBSFContext), |
197 | .init = mpeg4_unpack_bframes_init, |
198 | .filter = mpeg4_unpack_bframes_filter, |
199 | .close = mpeg4_unpack_bframes_close, |
200 | .codec_ids = codec_ids, |
201 | }; |
202 |