Skip to content

Commit 0cc8b61

Browse files
committed
feat: semi-aligned mp4mx output
1 parent 183cf9f commit 0cc8b61

File tree

1 file changed

+269
-16
lines changed

1 file changed

+269
-16
lines changed

src/lib/filters/mp4mx.c

+269-16
Original file line numberDiff line numberDiff line change
@@ -25,18 +25,81 @@
2525

2626
#include "lib/filters/filters.h"
2727
#include "lib/memio.h"
28+
#include <gpac/internal/isomedia_dev.h>
29+
30+
static guint32 INIT_BOXES[] = { GF_ISOM_BOX_TYPE_FTYP,
31+
GF_ISOM_BOX_TYPE_FREE,
32+
GF_ISOM_BOX_TYPE_MOOV,
33+
0 };
34+
static guint32 HEADER_BOXES[] = { GF_ISOM_BOX_TYPE_STYP,
35+
GF_ISOM_BOX_TYPE_MOOF,
36+
GF_ISOM_BOX_TYPE_MDAT,
37+
0 };
38+
39+
typedef enum _BufferType
40+
{
41+
INIT = 0,
42+
HEADER,
43+
DATA,
44+
45+
LAST
46+
} BufferType;
47+
48+
struct BoxMapping
49+
{
50+
BufferType type;
51+
guint32* boxes;
52+
} box_mapping[LAST] = {
53+
{ INIT, INIT_BOXES },
54+
{ HEADER, HEADER_BOXES },
55+
{ DATA, NULL },
56+
};
2857

2958
typedef struct
3059
{
60+
GstBuffer* buffer;
61+
gboolean is_complete;
62+
guint32 expected_size;
63+
guint64 pts, dts;
64+
guint32 duration;
65+
} BufferContents;
66+
67+
typedef struct
68+
{
69+
// Output queue for complete GOPs
3170
GQueue* output_queue;
71+
72+
// State
73+
BufferType current_type;
74+
75+
// Buffer contents for the init, header, and data
76+
BufferContents* contents[3];
77+
78+
// Global state
79+
gboolean is_discontinuity;
80+
81+
// Reader context
82+
guint64 offset;
83+
guint64 size;
84+
guint32 leftover;
3285
} Mp4mxCtx;
3386

3487
void
3588
mp4mx_ctx_init(void** process_ctx)
3689
{
3790
*process_ctx = g_new0(Mp4mxCtx, 1);
3891
Mp4mxCtx* ctx = (Mp4mxCtx*)*process_ctx;
92+
93+
// Initialize the context
3994
ctx->output_queue = g_queue_new();
95+
ctx->current_type = INIT;
96+
ctx->is_discontinuity = TRUE;
97+
98+
// Initialize the buffer contents
99+
for (guint i = 0; i < LAST; i++) {
100+
ctx->contents[i] = g_new0(BufferContents, 1);
101+
ctx->contents[i]->is_complete = FALSE;
102+
}
40103
}
41104

42105
void
@@ -49,6 +112,13 @@ mp4mx_ctx_free(void* process_ctx)
49112
gst_buffer_unref((GstBuffer*)g_queue_pop_head(ctx->output_queue));
50113
g_queue_free(ctx->output_queue);
51114

115+
// Free the buffer contents
116+
for (guint i = 0; i < LAST; i++) {
117+
if (ctx->contents[i]->buffer)
118+
gst_buffer_unref(ctx->contents[i]->buffer);
119+
g_free(ctx->contents[i]);
120+
}
121+
52122
// Free the context
53123
g_free(ctx);
54124
}
@@ -58,25 +128,208 @@ mp4mx_post_process(GF_Filter* filter, GF_FilterPacket* pck)
58128
{
59129
GPAC_MemIoContext* ctx = (GPAC_MemIoContext*)gf_filter_get_rt_udta(filter);
60130
Mp4mxCtx* mp4mx_ctx = (Mp4mxCtx*)ctx->process_ctx;
131+
if (!pck)
132+
return GF_OK;
61133

62134
// Get the data
63135
u32 size;
64136
const u8* data = gf_filter_pck_get_data(pck, &size);
65-
gf_filter_pck_ref(&pck);
66-
67-
// Create a new buffer
68-
GstBuffer* buffer =
69-
gst_buffer_new_wrapped_full(GST_MEMORY_FLAG_READONLY, // flags
70-
(u8*)data, // data
71-
size, // maxsize
72-
0, // offset
73-
size, // size
74-
pck, // user_data
75-
(GDestroyNotify)gf_filter_pck_unref // notify
76-
);
77-
78-
// Enqueue the buffer
79-
g_queue_push_tail(mp4mx_ctx->output_queue, buffer);
137+
138+
mp4mx_ctx->size += size;
139+
guint32 offset = 0;
140+
141+
// If we have leftover data, append it to the current buffer
142+
if (mp4mx_ctx->leftover) {
143+
guint32 leftover = MIN(mp4mx_ctx->leftover, size);
144+
GstMemory* mem =
145+
gst_memory_new_wrapped(GST_MEMORY_FLAG_READONLY,
146+
(gpointer)data,
147+
leftover,
148+
0,
149+
leftover,
150+
pck,
151+
(GDestroyNotify)gf_filter_pck_unref);
152+
gf_filter_pck_ref(&pck);
153+
154+
// Append the memory to the buffer
155+
g_assert(mp4mx_ctx->contents[mp4mx_ctx->current_type]->buffer);
156+
gst_buffer_append_memory(
157+
mp4mx_ctx->contents[mp4mx_ctx->current_type]->buffer, mem);
158+
159+
// Update the leftover and data
160+
mp4mx_ctx->leftover -= leftover;
161+
offset += leftover;
162+
}
163+
164+
// Iterate over the boxes
165+
while (offset < size) {
166+
guint32 box_size = GUINT32_FROM_BE(*(guint32*)(data + offset));
167+
guint32 box_type = GUINT32_FROM_BE(*(guint32*)(data + offset + 4));
168+
GST_WARNING("Box: %s, size: %u", gf_4cc_to_str(box_type), box_size);
169+
170+
// Special handling for mdat
171+
if (box_type == GF_ISOM_BOX_TYPE_MDAT) {
172+
// Create a new memory for the mdat header
173+
GstMemory* mem =
174+
gst_memory_new_wrapped(GST_MEMORY_FLAG_READONLY,
175+
(gpointer)data + offset,
176+
8,
177+
0,
178+
8,
179+
pck,
180+
(GDestroyNotify)gf_filter_pck_unref);
181+
gf_filter_pck_ref(&pck);
182+
183+
// Append the memory to the buffer
184+
g_assert(mp4mx_ctx->contents[HEADER]->buffer);
185+
gst_buffer_append_memory(mp4mx_ctx->contents[HEADER]->buffer, mem);
186+
187+
// Complete the header
188+
mp4mx_ctx->contents[HEADER]->is_complete = TRUE;
189+
mp4mx_ctx->current_type = DATA;
190+
191+
// Set the expected size
192+
mp4mx_ctx->contents[DATA]->expected_size = box_size - 8;
193+
194+
// Move the offset
195+
offset += 8;
196+
box_size -= 8;
197+
goto append_memory;
198+
}
199+
200+
guint32 last_type;
201+
for (last_type = mp4mx_ctx->current_type; last_type < LAST; last_type++) {
202+
// Check if the current box is related to current type
203+
gboolean found = FALSE;
204+
for (guint i = 0; box_mapping[last_type].boxes[i]; i++) {
205+
if (box_mapping[last_type].boxes[i] == box_type) {
206+
found = TRUE;
207+
break;
208+
}
209+
}
210+
if (found)
211+
break;
212+
213+
// If not found, current type is completed
214+
mp4mx_ctx->contents[last_type]->is_complete = TRUE;
215+
}
216+
217+
// Check if the box is unknown
218+
if (last_type == LAST) {
219+
GST_ERROR("Unknown box type: %s", gf_4cc_to_str(box_type));
220+
g_assert_not_reached();
221+
}
222+
223+
// Set the current type
224+
mp4mx_ctx->current_type = last_type;
225+
226+
append_memory:
227+
// Create a new memory
228+
GstMemory* mem =
229+
gst_memory_new_wrapped(GST_MEMORY_FLAG_READONLY,
230+
(gpointer)data + offset,
231+
box_size,
232+
0,
233+
box_size,
234+
pck,
235+
(GDestroyNotify)gf_filter_pck_unref);
236+
gf_filter_pck_ref(&pck);
237+
238+
// Append the memory to the buffer
239+
BufferType type = mp4mx_ctx->current_type;
240+
if (!mp4mx_ctx->contents[type]->buffer) {
241+
GstBuffer* buf = mp4mx_ctx->contents[type]->buffer = gst_buffer_new();
242+
243+
// Set the flags
244+
switch (type) {
245+
case INIT:
246+
if (mp4mx_ctx->is_discontinuity) {
247+
GST_BUFFER_FLAG_SET(buf, GST_BUFFER_FLAG_DISCONT);
248+
mp4mx_ctx->is_discontinuity = FALSE;
249+
}
250+
// fallthrough
251+
case HEADER:
252+
GST_BUFFER_FLAG_SET(buf, GST_BUFFER_FLAG_HEADER);
253+
break;
254+
case DATA:
255+
GST_BUFFER_FLAG_SET(buf, GST_BUFFER_FLAG_MARKER);
256+
GST_BUFFER_FLAG_SET(buf, GST_BUFFER_FLAG_DELTA_UNIT);
257+
break;
258+
259+
default:
260+
break;
261+
}
262+
263+
// Set the times
264+
GST_BUFFER_PTS(buf) = gf_filter_pck_get_cts(pck);
265+
GST_BUFFER_DTS(buf) = gf_filter_pck_get_dts(pck);
266+
GST_BUFFER_DURATION(buf) = gf_filter_pck_get_duration(pck);
267+
}
268+
269+
// Append the memory to the buffer
270+
gst_buffer_append_memory(mp4mx_ctx->contents[type]->buffer, mem);
271+
272+
// Move to the next box
273+
offset += box_size;
274+
}
275+
276+
// Update leftover and global offset
277+
if (mp4mx_ctx->offset + offset > mp4mx_ctx->size) {
278+
mp4mx_ctx->leftover = mp4mx_ctx->offset + offset - mp4mx_ctx->size;
279+
} else {
280+
g_assert(mp4mx_ctx->leftover == 0);
281+
mp4mx_ctx->offset = MIN(mp4mx_ctx->offset + offset, mp4mx_ctx->size);
282+
}
283+
284+
// Check if the data is complete
285+
if (mp4mx_ctx->contents[DATA]->buffer) {
286+
guint32 expected_size = mp4mx_ctx->contents[DATA]->expected_size;
287+
guint32 buffer_size =
288+
gst_buffer_get_size(mp4mx_ctx->contents[DATA]->buffer);
289+
290+
// Check if the buffer is complete
291+
if (buffer_size >= expected_size) {
292+
g_assert(buffer_size == expected_size);
293+
294+
// Update the state
295+
mp4mx_ctx->current_type = HEADER;
296+
mp4mx_ctx->contents[DATA]->is_complete = TRUE;
297+
}
298+
}
299+
300+
// Check if the GOP is complete
301+
if (mp4mx_ctx->contents[HEADER]->is_complete &&
302+
mp4mx_ctx->contents[DATA]->is_complete) {
303+
// Create a buffer list
304+
GstBufferList* buffer_list = gst_buffer_list_new();
305+
306+
// Init only if it's present
307+
if (mp4mx_ctx->contents[INIT]->is_complete) {
308+
gst_buffer_list_add(buffer_list, mp4mx_ctx->contents[INIT]->buffer);
309+
310+
// Init won't have timings set, set it using header
311+
GST_BUFFER_PTS(mp4mx_ctx->contents[INIT]->buffer) =
312+
GST_BUFFER_PTS(mp4mx_ctx->contents[HEADER]->buffer) -
313+
GST_BUFFER_DURATION(mp4mx_ctx->contents[HEADER]->buffer);
314+
GST_BUFFER_DTS(mp4mx_ctx->contents[INIT]->buffer) =
315+
GST_BUFFER_DTS(mp4mx_ctx->contents[HEADER]->buffer) -
316+
GST_BUFFER_DURATION(mp4mx_ctx->contents[HEADER]->buffer);
317+
}
318+
319+
gst_buffer_list_add(buffer_list, mp4mx_ctx->contents[HEADER]->buffer);
320+
gst_buffer_list_add(buffer_list, mp4mx_ctx->contents[DATA]->buffer);
321+
322+
// Enqueue the buffer
323+
g_queue_push_tail(mp4mx_ctx->output_queue, buffer_list);
324+
325+
// Reset the buffer contents
326+
for (guint i = 0; i < LAST; i++) {
327+
mp4mx_ctx->contents[i]->buffer = NULL;
328+
mp4mx_ctx->contents[i]->is_complete = FALSE;
329+
mp4mx_ctx->contents[i]->expected_size = 0;
330+
}
331+
}
332+
80333
return GF_OK;
81334
}
82335

@@ -93,5 +346,5 @@ mp4mx_consume(GF_Filter* filter, void** outptr)
93346

94347
// Assign the output
95348
*outptr = g_queue_pop_head(mp4mx_ctx->output_queue);
96-
return GPAC_FILTER_PP_RET_BUFFER;
349+
return GPAC_FILTER_PP_RET_BUFFER_LIST;
97350
}

0 commit comments

Comments
 (0)