Line data Source code
1 : /*
2 : * GPAC - Multimedia Framework C SDK
3 : *
4 : * Authors: Deniz Ugur, Romain Bouqueau, Sohaib Larbi
5 : * Copyright (c) Motion Spell
6 : * All rights reserved
7 : *
8 : * This file is part of the GPAC/GStreamer wrapper
9 : *
10 : * This GPAC/GStreamer wrapper is free software; you can redistribute it
11 : * and/or modify it under the terms of the GNU Affero General Public License
12 : * as published by the Free Software Foundation; either version 3, or (at
13 : * your option) any later version.
14 : *
15 : * This GPAC/GStreamer wrapper is distributed in the hope that it will be
16 : * useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
17 : * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 : * GNU Affero General Public License for more details.
19 : *
20 : * You should have received a copy of the GNU Affero General Public
21 : * License along with this library; see the file LICENSE. If not, write to
22 : * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
23 : *
24 : */
25 :
26 : #include "common.h"
27 : #include "elements/gstgpactf.h"
28 : #include "lib/memio.h"
29 : #include <gpac/internal/isomedia_dev.h>
30 :
31 : GST_DEBUG_CATEGORY_STATIC(gpac_mp4mx);
32 : #define GST_CAT_DEFAULT gpac_mp4mx
33 :
34 : #define GET_TYPE(type) mp4mx_ctx->contents[type]
35 :
36 : static guint32 INIT_BOXES[] = { GF_ISOM_BOX_TYPE_FTYP,
37 : GF_ISOM_BOX_TYPE_FREE,
38 : GF_ISOM_BOX_TYPE_MOOV,
39 : 0 };
40 : static guint32 HEADER_BOXES[] = { GF_ISOM_BOX_TYPE_STYP,
41 : GF_ISOM_BOX_TYPE_SIDX,
42 : GF_ISOM_BOX_TYPE_MOOF,
43 : 0 };
44 : static guint32 DATA_BOXES[] = { GF_ISOM_BOX_TYPE_MDAT, 0 };
45 :
46 : typedef enum _BufferType
47 : {
48 : INIT = 0,
49 : HEADER,
50 : DATA,
51 :
52 : LAST
53 : } BufferType;
54 :
55 : struct BoxMapping
56 : {
57 : BufferType type;
58 : guint32* boxes;
59 : } box_mapping[LAST] = {
60 : { INIT, INIT_BOXES },
61 : { HEADER, HEADER_BOXES },
62 : { DATA, DATA_BOXES },
63 : };
64 :
65 : typedef struct
66 : {
67 : GstBuffer* buffer;
68 : gboolean is_complete;
69 : } BufferContents;
70 :
71 : typedef struct
72 : {
73 : guint32 box_type;
74 : guint32 box_size;
75 : GstBuffer* buffer;
76 : gboolean parsed;
77 : } BoxInfo;
78 :
79 : typedef struct
80 : {
81 : guint32 track_id;
82 : guint32 timescale;
83 : gboolean defaults_present;
84 : guint32 default_sample_duration;
85 : guint32 default_sample_size;
86 : guint32 default_sample_flags;
87 : } TrackInfo;
88 :
89 : typedef struct
90 : {
91 : gboolean is_sync;
92 : gssize size;
93 : guint64 pts;
94 : guint64 dts;
95 : guint64 duration;
96 : } SampleInfo;
97 :
98 : typedef struct
99 : {
100 : // Output queue for complete fragments
101 : GQueue* output_queue;
102 :
103 : // State
104 : BufferType current_type;
105 : guint32 segment_count;
106 :
107 : // Box parser state
108 : GQueue* box_queue;
109 :
110 : // Buffer contents for the init, header, and data
111 : BufferContents* contents[3];
112 :
113 : // Input context
114 : guint64 duration;
115 : guint64 mp4mx_ts;
116 : GHashTable* tracks;
117 : GArray* next_samples;
118 : } Mp4mxCtx;
119 :
120 : void
121 9 : mp4mx_ctx_init(void** process_ctx)
122 : {
123 9 : *process_ctx = g_new0(Mp4mxCtx, 1);
124 9 : Mp4mxCtx* ctx = (Mp4mxCtx*)*process_ctx;
125 :
126 9 : GST_DEBUG_CATEGORY_INIT(
127 : gpac_mp4mx, "gpacmp4mxpp", 0, "GPAC mp4mx post-processor");
128 :
129 : // Initialize the context
130 9 : ctx->output_queue = g_queue_new();
131 9 : ctx->current_type = INIT;
132 9 : ctx->segment_count = 0;
133 9 : ctx->box_queue = g_queue_new();
134 :
135 : // Allocate tracks and next samples
136 9 : ctx->tracks =
137 9 : g_hash_table_new_full(g_direct_hash, g_direct_equal, NULL, g_free);
138 9 : ctx->next_samples = g_array_new(FALSE, TRUE, sizeof(SampleInfo));
139 :
140 : // Initialize the buffer contents
141 36 : for (guint i = 0; i < LAST; i++) {
142 27 : ctx->contents[i] = g_new0(BufferContents, 1);
143 27 : ctx->contents[i]->is_complete = FALSE;
144 : }
145 9 : }
146 :
147 : void
148 0 : mp4mx_ctx_free(void* process_ctx)
149 : {
150 0 : Mp4mxCtx* ctx = (Mp4mxCtx*)process_ctx;
151 :
152 : // Free the output queue
153 0 : while (!g_queue_is_empty(ctx->output_queue))
154 0 : gst_buffer_unref((GstBuffer*)g_queue_pop_head(ctx->output_queue));
155 0 : g_queue_free(ctx->output_queue);
156 :
157 : // Free the box queue
158 0 : while (!g_queue_is_empty(ctx->box_queue)) {
159 0 : BoxInfo* buf = g_queue_pop_head(ctx->box_queue);
160 0 : if (buf->buffer)
161 0 : gst_buffer_unref(buf->buffer);
162 : }
163 0 : g_queue_free(ctx->box_queue);
164 :
165 : // Free the buffer contents
166 0 : for (guint i = 0; i < LAST; i++) {
167 0 : if (ctx->contents[i]->buffer)
168 0 : gst_buffer_unref(ctx->contents[i]->buffer);
169 0 : g_free(ctx->contents[i]);
170 : }
171 :
172 : // Free the tracks and next samples
173 0 : g_hash_table_destroy(ctx->tracks);
174 0 : g_array_free(ctx->next_samples, TRUE);
175 :
176 : // Free the context
177 0 : g_free(ctx);
178 0 : }
179 :
180 : GF_Err
181 9 : mp4mx_configure_pid(GF_Filter* filter, GF_FilterPid* pid)
182 : {
183 : GPAC_MemOutPIDContext* pctx =
184 9 : (GPAC_MemOutPIDContext*)gf_filter_pid_get_udta(pid);
185 9 : Mp4mxCtx* mp4mx_ctx = (Mp4mxCtx*)pctx->private_ctx;
186 :
187 : // Get the timescale from the PID
188 9 : mp4mx_ctx->mp4mx_ts = GST_SECOND;
189 : const GF_PropertyValue* p =
190 9 : gf_filter_pid_get_property(pid, GF_PROP_PID_TIMESCALE);
191 9 : if (p)
192 9 : mp4mx_ctx->mp4mx_ts = p->value.uint;
193 :
194 9 : return GF_OK;
195 : }
196 :
197 : Bool
198 0 : mp4mx_process_event(GF_Filter* filter, const GF_FilterEvent* evt)
199 : {
200 0 : return GF_FALSE; // No event processing
201 : }
202 :
203 : GstMemory*
204 115 : mp4mx_create_memory(const u8* data, guint32 size, GF_FilterPacket* pck)
205 : {
206 115 : gf_filter_pck_ref(&pck);
207 115 : return gst_memory_new_wrapped(GST_MEMORY_FLAG_READONLY,
208 : (gpointer)data,
209 : size,
210 : 0,
211 : size,
212 : pck,
213 : (GDestroyNotify)gf_filter_pck_unref);
214 : }
215 :
216 : gboolean
217 408 : mp4mx_is_box_complete(BoxInfo* box)
218 : {
219 816 : return box && box->buffer &&
220 408 : gst_buffer_get_size(box->buffer) == box->box_size;
221 : }
222 :
223 : GF_Err
224 9 : mp4mx_parse_moov(GF_Filter* filter, GF_FilterPid* pid, GstBuffer* buffer)
225 : {
226 9 : GPAC_MemIoContext* ctx = (GPAC_MemIoContext*)gf_filter_get_rt_udta(filter);
227 : GPAC_MemOutPIDContext* pctx =
228 9 : (GPAC_MemOutPIDContext*)gf_filter_pid_get_udta(pid);
229 9 : Mp4mxCtx* mp4mx_ctx = (Mp4mxCtx*)pctx->private_ctx;
230 :
231 : // Map the buffer
232 9 : g_auto(GstBufferMapInfo) map = GST_MAP_INFO_INIT;
233 9 : if (G_UNLIKELY(!gst_buffer_map(buffer, &map, GST_MAP_READ))) {
234 0 : GST_ELEMENT_ERROR(ctx->sess->element,
235 : STREAM,
236 : FAILED,
237 : (NULL),
238 : ("Failed to map moov buffer"));
239 0 : return GF_CORRUPTED_DATA;
240 : }
241 :
242 : // Parse the box
243 9 : GF_BitStream* bs = gf_bs_new(map.data, map.size, GF_BITSTREAM_READ);
244 9 : GF_Box* moov = NULL;
245 9 : GF_Err err = gf_isom_box_parse(&moov, bs);
246 9 : if (err != GF_OK) {
247 0 : GST_ELEMENT_ERROR(
248 : ctx->sess->element, STREAM, FAILED, (NULL), ("Failed to parse moov box"));
249 0 : return GF_CORRUPTED_DATA;
250 : }
251 :
252 : // Check if we have duration in the mvhd box
253 9 : GF_MovieHeaderBox* mvhd = (GF_MovieHeaderBox*)gf_isom_box_find_child(
254 9 : moov->child_boxes, GF_ISOM_BOX_TYPE_MVHD);
255 9 : if (mvhd) {
256 9 : mp4mx_ctx->duration =
257 9 : gf_timestamp_rescale(mvhd->duration, mvhd->timeScale, GST_SECOND);
258 : }
259 :
260 : // Go through all tracks
261 9 : guint32 count = gf_list_count(moov->child_boxes);
262 54 : for (guint32 i = 0; i < count; i++) {
263 45 : GF_Box* box = (GF_Box*)gf_list_get(moov->child_boxes, i);
264 45 : if (box->type != GF_ISOM_BOX_TYPE_TRAK)
265 35 : continue;
266 :
267 : // Create new tracks
268 10 : TrackInfo* track = g_new0(TrackInfo, 1);
269 :
270 : // Get the track id
271 10 : GF_TrackHeaderBox* tkhd = (GF_TrackHeaderBox*)gf_isom_box_find_child(
272 : box->child_boxes, GF_ISOM_BOX_TYPE_TKHD);
273 10 : track->track_id = tkhd->trackID;
274 :
275 : // Get the timescale
276 : GF_Box* mdia =
277 10 : gf_isom_box_find_child(box->child_boxes, GF_ISOM_BOX_TYPE_MDIA);
278 10 : GF_MediaHeaderBox* mdhd = (GF_MediaHeaderBox*)gf_isom_box_find_child(
279 : mdia->child_boxes, GF_ISOM_BOX_TYPE_MDHD);
280 10 : track->timescale = mdhd->timeScale;
281 :
282 10 : GST_DEBUG_OBJECT(ctx->sess->element,
283 : "Found track %d with timescale %d",
284 : track->track_id,
285 : track->timescale);
286 :
287 : // Find the mvex for this track
288 61 : for (guint32 j = 0; j < gf_list_count(moov->child_boxes); j++) {
289 51 : GF_Box* mvex = (GF_Box*)gf_list_get(moov->child_boxes, j);
290 51 : if (mvex->type != GF_ISOM_BOX_TYPE_MVEX)
291 42 : continue;
292 :
293 : // Find the track fragment box
294 29 : for (guint32 k = 0; k < gf_list_count(mvex->child_boxes); k++) {
295 : GF_TrackExtendsBox* trex =
296 20 : (GF_TrackExtendsBox*)gf_list_get(mvex->child_boxes, k);
297 20 : if (trex->type != GF_ISOM_BOX_TYPE_TREX)
298 9 : continue;
299 :
300 : // Check if this is the right track
301 11 : if (trex->trackID != track->track_id)
302 2 : continue;
303 :
304 : // Set the defaults
305 9 : track->defaults_present = TRUE;
306 9 : track->default_sample_duration = trex->def_sample_duration;
307 9 : track->default_sample_size = trex->def_sample_size;
308 9 : track->default_sample_flags = trex->def_sample_flags;
309 :
310 9 : GST_DEBUG_OBJECT(ctx->sess->element,
311 : "Found defaults for track %d: duration: %d, size: %d, "
312 : "flags: %d",
313 : track->track_id,
314 : track->default_sample_duration,
315 : track->default_sample_size,
316 : track->default_sample_flags);
317 : }
318 : }
319 :
320 : // Add the track to the hash table
321 10 : g_hash_table_insert(
322 10 : mp4mx_ctx->tracks, GUINT_TO_POINTER(track->track_id), track);
323 : }
324 :
325 : // Free the box
326 9 : gf_isom_box_del(moov);
327 9 : gf_bs_del(bs);
328 :
329 9 : return GF_OK;
330 : }
331 :
332 : GF_Err
333 30 : mp4mx_parse_moof(GF_Filter* filter, GF_FilterPid* pid, GstBuffer* buffer)
334 : {
335 30 : GPAC_MemIoContext* ctx = (GPAC_MemIoContext*)gf_filter_get_rt_udta(filter);
336 : GPAC_MemOutPIDContext* pctx =
337 30 : (GPAC_MemOutPIDContext*)gf_filter_pid_get_udta(pid);
338 30 : Mp4mxCtx* mp4mx_ctx = (Mp4mxCtx*)pctx->private_ctx;
339 :
340 : // Map the buffer
341 30 : g_auto(GstBufferMapInfo) map = GST_MAP_INFO_INIT;
342 30 : if (G_UNLIKELY(!gst_buffer_map(buffer, &map, GST_MAP_READ))) {
343 0 : GST_ELEMENT_ERROR(ctx->sess->element,
344 : STREAM,
345 : FAILED,
346 : (NULL),
347 : ("Failed to map moof buffer"));
348 0 : return GF_CORRUPTED_DATA;
349 : }
350 :
351 : // Parse the box
352 30 : GF_BitStream* bs = gf_bs_new(map.data, map.size, GF_BITSTREAM_READ);
353 30 : GF_Box* moof = NULL;
354 30 : GF_Err err = gf_isom_box_parse(&moof, bs);
355 30 : if (err != GF_OK) {
356 0 : GST_ELEMENT_ERROR(
357 : ctx->sess->element, STREAM, FAILED, (NULL), ("Failed to parse moof box"));
358 0 : return GF_CORRUPTED_DATA;
359 : }
360 :
361 : // Check if there are multiple tracks
362 30 : guint32 count = 0;
363 89 : for (guint32 i = 0; i < gf_list_count(moof->child_boxes); i++) {
364 59 : GF_Box* box = (GF_Box*)gf_list_get(moof->child_boxes, i);
365 59 : if (box->type == GF_ISOM_BOX_TYPE_TRAF)
366 29 : count++;
367 : }
368 30 : if (count > 1) {
369 0 : GST_FIXME_OBJECT(ctx->sess->element,
370 : "Multiple tracks in moof not supported yet");
371 0 : return GF_NOT_SUPPORTED;
372 : }
373 :
374 : // Find the required boxes
375 : GF_Box* traf =
376 30 : gf_isom_box_find_child(moof->child_boxes, GF_ISOM_BOX_TYPE_TRAF);
377 30 : if (!traf) {
378 1 : GST_DEBUG_OBJECT(ctx->sess->element, "No traf box found");
379 1 : g_array_set_size(mp4mx_ctx->next_samples, 0);
380 1 : goto empty_moof;
381 : }
382 :
383 : GF_TrackFragmentHeaderBox* tfhd =
384 29 : (GF_TrackFragmentHeaderBox*)gf_isom_box_find_child(traf->child_boxes,
385 : GF_ISOM_BOX_TYPE_TFHD);
386 : GF_TFBaseMediaDecodeTimeBox* tfdt =
387 29 : (GF_TFBaseMediaDecodeTimeBox*)gf_isom_box_find_child(traf->child_boxes,
388 : GF_ISOM_BOX_TYPE_TFDT);
389 : GF_TrackFragmentRunBox* trun =
390 29 : (GF_TrackFragmentRunBox*)gf_isom_box_find_child(traf->child_boxes,
391 : GF_ISOM_BOX_TYPE_TRUN);
392 :
393 : // Get the track info
394 : TrackInfo* track =
395 29 : g_hash_table_lookup(mp4mx_ctx->tracks, GUINT_TO_POINTER(tfhd->trackID));
396 29 : if (!track) {
397 0 : GST_ERROR_OBJECT(ctx->sess->element, "Track %d not found", tfhd->trackID);
398 0 : return GF_BAD_PARAM;
399 : }
400 :
401 : // Declare the defaults
402 29 : guint32 default_sample_duration = track->default_sample_duration;
403 29 : guint32 default_sample_size = track->default_sample_size;
404 29 : guint32 default_sample_flags = track->default_sample_flags;
405 :
406 : // Look at the track fragment header box
407 29 : gboolean default_sample_duration_present = (tfhd->flags & 0x8) == 0x8;
408 29 : gboolean default_sample_size_present = (tfhd->flags & 0x10) == 0x10;
409 29 : gboolean default_sample_flags_present = (tfhd->flags & 0x20) == 0x20;
410 :
411 29 : if (default_sample_duration_present)
412 28 : default_sample_duration = tfhd->def_sample_duration;
413 29 : if (default_sample_size_present)
414 2 : default_sample_size = tfhd->def_sample_size;
415 29 : if (default_sample_flags_present)
416 19 : default_sample_flags = tfhd->def_sample_flags;
417 :
418 : // Resize the next samples array
419 29 : g_array_set_size(mp4mx_ctx->next_samples, trun->sample_count);
420 :
421 : // Look at all samples
422 1069 : for (guint32 i = 0; i < trun->sample_count; i++) {
423 1040 : GF_TrunEntry* entry = &trun->samples[i];
424 1040 : SampleInfo* sample = &g_array_index(mp4mx_ctx->next_samples, SampleInfo, i);
425 :
426 : // Check if this is a sync sample
427 1040 : gboolean first_sample_flags_present = (trun->flags & 0x4) == 0x4;
428 1040 : gboolean sample_flags_present = (trun->flags & 0x400) == 0x400;
429 :
430 1040 : u32 flags = 0;
431 1040 : if (i == 0 && first_sample_flags_present) {
432 0 : flags = trun->first_sample_flags;
433 1040 : } else if (sample_flags_present) {
434 514 : flags = entry->flags;
435 526 : } else if (default_sample_flags_present || track->defaults_present) {
436 526 : flags = default_sample_flags;
437 : } else {
438 0 : GST_ERROR_OBJECT(ctx->sess->element, "No sample flags found");
439 0 : return GF_CORRUPTED_DATA;
440 : }
441 :
442 1040 : sample->is_sync = GF_ISOM_GET_FRAG_SYNC(flags);
443 :
444 : // Retrieve the sample size and offset
445 1040 : gboolean sample_size_present = (trun->flags & 0x200) == 0x200;
446 :
447 1040 : if (sample_size_present) {
448 1038 : sample->size = entry->size;
449 2 : } else if (default_sample_size_present || track->defaults_present) {
450 2 : sample->size = default_sample_size;
451 : } else {
452 0 : GST_ERROR_OBJECT(ctx->sess->element, "No sample size found");
453 0 : return GF_CORRUPTED_DATA;
454 : }
455 :
456 : // Retrieve the sample duration
457 1040 : gboolean sample_duration_present = (trun->flags & 0x100) == 0x100;
458 :
459 1040 : guint64 duration = 0;
460 1040 : if (sample_duration_present) {
461 44 : duration = entry->Duration;
462 996 : } else if (default_sample_duration_present || track->defaults_present) {
463 996 : duration = default_sample_duration;
464 : } else {
465 0 : GST_ERROR_OBJECT(ctx->sess->element, "No sample duration found");
466 0 : return GF_CORRUPTED_DATA;
467 : }
468 :
469 1040 : sample->duration =
470 1040 : gf_timestamp_rescale(duration, track->timescale, GST_SECOND);
471 :
472 : // Retrieve the sample DTS
473 1040 : guint64 dts = tfdt->baseMediaDecodeTime + i;
474 1040 : sample->dts = gf_timestamp_rescale(dts, track->timescale, GST_SECOND);
475 1040 : sample->dts += ctx->global_offset;
476 :
477 : // Retrieve the sample PTS
478 1040 : guint64 pts = dts + entry->CTS_Offset;
479 1040 : sample->pts = gf_timestamp_rescale(pts, track->timescale, GST_SECOND);
480 1040 : sample->pts += ctx->global_offset;
481 :
482 1040 : GST_TRACE_OBJECT(ctx->sess->element,
483 : "Sample %d [%s]: size: %" G_GSSIZE_FORMAT ", "
484 : "duration: %" GST_TIME_FORMAT ", "
485 : "DTS: %" GST_TIME_FORMAT ", "
486 : "PTS: %" GST_TIME_FORMAT,
487 : i,
488 : sample->is_sync ? "S" : "NS",
489 : sample->size,
490 : GST_TIME_ARGS(sample->duration),
491 : GST_TIME_ARGS(sample->dts),
492 : GST_TIME_ARGS(sample->pts));
493 : }
494 :
495 29 : empty_moof:
496 : // Free the box
497 30 : gf_isom_box_del(moof);
498 30 : gf_bs_del(bs);
499 :
500 30 : return GF_OK;
501 : }
502 :
503 : gboolean
504 97 : mp4mx_parse_boxes(GF_Filter* filter, GF_FilterPid* pid, GF_FilterPacket* pck)
505 : {
506 97 : GPAC_MemIoContext* ctx = (GPAC_MemIoContext*)gf_filter_get_rt_udta(filter);
507 : GPAC_MemOutPIDContext* pctx =
508 97 : (GPAC_MemOutPIDContext*)gf_filter_pid_get_udta(pid);
509 97 : Mp4mxCtx* mp4mx_ctx = (Mp4mxCtx*)pctx->private_ctx;
510 :
511 : // Get the data
512 : u32 size;
513 97 : const u8* data = gf_filter_pck_get_data(pck, &size);
514 97 : GST_DEBUG_OBJECT(
515 : ctx->sess->element, "Processing data of size %" G_GUINT32_FORMAT, size);
516 :
517 97 : guint32 offset = 0;
518 300 : while (offset < size) {
519 203 : BoxInfo* box = g_queue_peek_tail(mp4mx_ctx->box_queue);
520 203 : if (!box || mp4mx_is_box_complete(box)) {
521 88 : box = g_new0(BoxInfo, 1);
522 88 : g_queue_push_tail(mp4mx_ctx->box_queue, box);
523 : }
524 :
525 : // If we have leftover data, append it to the current buffer
526 203 : if (box->buffer && gst_buffer_get_size(box->buffer) != box->box_size) {
527 345 : guint32 leftover =
528 115 : MIN(box->box_size - gst_buffer_get_size(box->buffer), size - offset);
529 115 : if (gst_buffer_get_size(box->buffer) > 0)
530 27 : GST_DEBUG_OBJECT(ctx->sess->element,
531 : "Incomplete box %s, appending %" G_GUINT32_FORMAT
532 : " bytes",
533 : gf_4cc_to_str(box->box_type),
534 : leftover);
535 115 : GstMemory* mem = mp4mx_create_memory(data + offset, leftover, pck);
536 :
537 : // Append the memory to the buffer
538 115 : gst_buffer_append_memory(box->buffer, mem);
539 :
540 : // Update the offset
541 115 : offset += leftover;
542 115 : GST_DEBUG_OBJECT(ctx->sess->element,
543 : "Wrote %" G_GSIZE_FORMAT " bytes of %" G_GUINT32_FORMAT
544 : " for box %s",
545 : gst_buffer_get_size(box->buffer),
546 : box->box_size,
547 : gf_4cc_to_str(box->box_type));
548 115 : continue;
549 : }
550 :
551 : // Parse the box header
552 88 : box->box_size =
553 88 : GUINT32_FROM_LE((data[offset] << 24) | (data[offset + 1] << 16) |
554 : (data[offset + 2] << 8) | data[offset + 3]);
555 88 : box->box_type =
556 88 : GUINT32_FROM_LE((data[offset + 4] << 24) | (data[offset + 5] << 16) |
557 : (data[offset + 6] << 8) | data[offset + 7]);
558 88 : GST_DEBUG_OBJECT(ctx->sess->element,
559 : "Saw box %s with size %" G_GUINT32_FORMAT,
560 : gf_4cc_to_str(box->box_type),
561 : box->box_size);
562 :
563 : // Create a new buffer
564 88 : box->buffer = gst_buffer_new();
565 :
566 : // Preserve the timing information
567 88 : GST_BUFFER_PTS(box->buffer) = gf_filter_pck_get_cts(pck);
568 88 : GST_BUFFER_DTS(box->buffer) = gf_filter_pck_get_dts(pck);
569 88 : GST_BUFFER_DURATION(box->buffer) = gf_filter_pck_get_duration(pck);
570 : }
571 :
572 : // Check if process can continue
573 97 : BoxInfo* box = g_queue_peek_head(mp4mx_ctx->box_queue);
574 97 : if (!mp4mx_is_box_complete(box))
575 26 : return FALSE;
576 :
577 : // Check if we need any further parsing
578 160 : for (guint i = 0; i < g_queue_get_length(mp4mx_ctx->box_queue); i++) {
579 89 : BoxInfo* box = g_queue_peek_nth(mp4mx_ctx->box_queue, i);
580 89 : if (!mp4mx_is_box_complete(box) || box->parsed)
581 1 : continue;
582 :
583 88 : switch (box->box_type) {
584 9 : case GF_ISOM_BOX_TYPE_MOOV:
585 9 : gpac_return_val_if_fail(mp4mx_parse_moov(filter, pid, box->buffer),
586 : FALSE);
587 9 : break;
588 :
589 30 : case GF_ISOM_BOX_TYPE_MOOF:
590 30 : gpac_return_val_if_fail(mp4mx_parse_moof(filter, pid, box->buffer),
591 : FALSE);
592 30 : break;
593 :
594 49 : default:
595 49 : break;
596 : }
597 :
598 88 : box->parsed = TRUE;
599 : }
600 :
601 71 : return TRUE;
602 : }
603 :
604 : GstBufferList*
605 31 : mp4mx_create_buffer_list(GF_Filter* filter, GF_FilterPid* pid)
606 : {
607 31 : GPAC_MemIoContext* ctx = (GPAC_MemIoContext*)gf_filter_get_rt_udta(filter);
608 31 : GstGpacTransform* gpac_tf = GST_GPAC_TF(GST_ELEMENT(ctx->sess->element));
609 : GPAC_MemOutPIDContext* pctx =
610 31 : (GPAC_MemOutPIDContext*)gf_filter_pid_get_udta(pid);
611 31 : Mp4mxCtx* mp4mx_ctx = (Mp4mxCtx*)pctx->private_ctx;
612 :
613 : // Check if the init and header buffers are present
614 31 : gboolean init_present = GET_TYPE(INIT)->is_complete && GET_TYPE(INIT)->buffer;
615 31 : gboolean header_present =
616 31 : GET_TYPE(HEADER)->is_complete && GET_TYPE(HEADER)->buffer;
617 :
618 : // Declare variables
619 31 : GstMemory* mdat_hdr = NULL;
620 31 : gboolean segment_boundary = FALSE;
621 :
622 : // Create a new buffer list
623 31 : GstBufferList* buffer_list = gst_buffer_list_new();
624 31 : GST_DEBUG_OBJECT(ctx->sess->element, "Fragment completed");
625 :
626 : //
627 : // Split the DATA buffer into samples, if we have the sample information
628 : //
629 :
630 : // Copy the data as is if we don't have sample information
631 31 : gboolean has_sample_info = mp4mx_ctx->next_samples->len > 0;
632 31 : if (!has_sample_info) {
633 2 : GST_DEBUG_OBJECT(
634 : ctx->sess->element,
635 : "No sample information found, appending data buffer as is");
636 :
637 : // Set the flags
638 2 : GST_BUFFER_FLAG_SET(GET_TYPE(DATA)->buffer, GST_BUFFER_FLAG_MARKER);
639 2 : GST_BUFFER_FLAG_SET(GET_TYPE(DATA)->buffer, GST_BUFFER_FLAG_DELTA_UNIT);
640 :
641 : // size of the data buffer
642 2 : guint64 size = gst_buffer_get_size(GET_TYPE(DATA)->buffer);
643 :
644 : // We have to rely on mp4mx timing information
645 :
646 : // PTS
647 2 : guint64 pts = gf_timestamp_rescale(
648 2 : GST_BUFFER_PTS(GET_TYPE(DATA)->buffer), mp4mx_ctx->mp4mx_ts, GST_SECOND);
649 2 : pts += ctx->global_offset;
650 2 : GST_BUFFER_PTS(GET_TYPE(DATA)->buffer) = pts;
651 :
652 : // DTS
653 2 : guint64 dts = gf_timestamp_rescale(
654 2 : GST_BUFFER_DTS(GET_TYPE(DATA)->buffer), mp4mx_ctx->mp4mx_ts, GST_SECOND);
655 2 : dts += ctx->global_offset;
656 2 : GST_BUFFER_DTS(GET_TYPE(DATA)->buffer) = dts;
657 :
658 : // Duration
659 : // For duration, we have to use mvhd since we don't parse the moov for
660 : // sample informations
661 2 : GST_BUFFER_DURATION(GET_TYPE(DATA)->buffer) = mp4mx_ctx->duration;
662 :
663 2 : gst_buffer_list_add(buffer_list, GET_TYPE(DATA)->buffer);
664 2 : goto headers;
665 : }
666 :
667 : // Move the mdat header out of the data buffer
668 : mdat_hdr =
669 29 : gst_memory_share(gst_buffer_peek_memory(GET_TYPE(DATA)->buffer, 0), 0, 8);
670 29 : gst_buffer_resize(GET_TYPE(DATA)->buffer, 8, -1);
671 :
672 : // Go through all samples
673 29 : gssize data_offset = 0;
674 1069 : for (guint s = 0; s < mp4mx_ctx->next_samples->len; s++) {
675 1040 : SampleInfo* sample = &g_array_index(mp4mx_ctx->next_samples, SampleInfo, s);
676 :
677 : // Create a new buffer
678 1040 : GstBuffer* sample_buffer = gst_buffer_new();
679 :
680 : // Slice the buffer
681 1040 : gssize avail_sample_size = sample->size;
682 1040 : gssize offset = 0;
683 1040 : guint n_blocks = gst_buffer_n_memory(GET_TYPE(DATA)->buffer);
684 2080 : for (guint b = 0; b < n_blocks && avail_sample_size; b++) {
685 1040 : GstMemory* mem = gst_buffer_peek_memory(GET_TYPE(DATA)->buffer, b);
686 1040 : gssize size = (gssize)gst_memory_get_sizes(mem, NULL, NULL);
687 :
688 : // Skip until we reach the data offset
689 1040 : if (offset + size <= data_offset)
690 0 : goto skip;
691 :
692 : // Check the offset within the block
693 1040 : gssize block_offset = 0;
694 1040 : if (offset < data_offset) {
695 1011 : block_offset = data_offset - offset;
696 1011 : offset += block_offset;
697 1011 : size -= block_offset;
698 : }
699 :
700 : // Check if the block is enough
701 1040 : if (size <= avail_sample_size) {
702 29 : gst_buffer_append_memory(sample_buffer,
703 : gst_memory_share(mem, block_offset, size));
704 29 : avail_sample_size -= size;
705 : } else {
706 1011 : gst_buffer_append_memory(
707 : sample_buffer,
708 : gst_memory_share(mem, block_offset, avail_sample_size));
709 1011 : avail_sample_size = 0;
710 : }
711 :
712 1040 : skip:
713 : // Update the local offset
714 1040 : offset += size;
715 : }
716 :
717 : // Set the marker flag if it's the last sample
718 1040 : if (s == mp4mx_ctx->next_samples->len - 1)
719 29 : GST_BUFFER_FLAG_SET(sample_buffer, GST_BUFFER_FLAG_MARKER);
720 :
721 : // Set the delta unit flag. These buffers are always delta because they
722 : // follow a moof
723 1040 : GST_BUFFER_FLAG_SET(sample_buffer, GST_BUFFER_FLAG_DELTA_UNIT);
724 1040 : if (sample->is_sync)
725 55 : segment_boundary = TRUE;
726 :
727 : // Set the PTS, DTS, and duration
728 1040 : GST_BUFFER_PTS(sample_buffer) = sample->pts;
729 1040 : GST_BUFFER_DTS(sample_buffer) = sample->dts;
730 1040 : GST_BUFFER_DURATION(sample_buffer) = sample->duration;
731 :
732 : // Append the sample buffer
733 1040 : gst_buffer_list_add(buffer_list, sample_buffer);
734 :
735 1040 : GST_TRACE_OBJECT(
736 : ctx->sess->element,
737 : "Added sample %d to the buffer list: size: %" G_GSSIZE_FORMAT ", "
738 : "duration: %" GST_TIME_FORMAT ", "
739 : "DTS: %" GST_TIME_FORMAT ", "
740 : "PTS: %" GST_TIME_FORMAT,
741 : s,
742 : sample->size,
743 : GST_TIME_ARGS(sample->duration),
744 : GST_TIME_ARGS(sample->dts),
745 : GST_TIME_ARGS(sample->pts));
746 :
747 : // Update the data offset
748 1040 : data_offset += sample->size;
749 : }
750 :
751 : // Unref the data buffer
752 29 : gst_buffer_unref(GET_TYPE(DATA)->buffer);
753 :
754 31 : headers:
755 : // Add the init buffer if it's present
756 31 : if (init_present) {
757 9 : GST_DEBUG_OBJECT(ctx->sess->element, "Adding init buffer to the beginning");
758 :
759 : // Set the flags
760 9 : GST_BUFFER_FLAG_SET(GET_TYPE(INIT)->buffer, GST_BUFFER_FLAG_HEADER);
761 9 : if (mp4mx_ctx->segment_count == 0) {
762 9 : GST_BUFFER_FLAG_SET(GET_TYPE(INIT)->buffer, GST_BUFFER_FLAG_DISCONT);
763 9 : ctx->is_continuous = TRUE;
764 : }
765 :
766 : // Set the timing information
767 9 : guint64 pts = G_MAXUINT64;
768 9 : guint64 dts = G_MAXUINT64;
769 9 : if (has_sample_info) {
770 : // Set PTS and DTS to the minimum of the samples
771 16 : for (guint s = 0; s < mp4mx_ctx->next_samples->len; s++) {
772 16 : SampleInfo* sample =
773 16 : &g_array_index(mp4mx_ctx->next_samples, SampleInfo, s);
774 :
775 16 : dts = MIN(dts, sample->dts);
776 16 : if (sample->pts < pts)
777 8 : pts = sample->pts;
778 : else {
779 : // PTS can only decrease if there are B-frames, so we break
780 8 : break;
781 : }
782 : }
783 : } else {
784 : // Set the PTS and DTS to the minimum of the data
785 1 : pts = GST_BUFFER_PTS(GET_TYPE(DATA)->buffer);
786 1 : dts = GST_BUFFER_DTS(GET_TYPE(DATA)->buffer);
787 : }
788 :
789 9 : GST_BUFFER_PTS(GET_TYPE(INIT)->buffer) = pts;
790 9 : GST_BUFFER_DTS(GET_TYPE(INIT)->buffer) = dts;
791 9 : GST_BUFFER_DURATION(GET_TYPE(INIT)->buffer) = GST_CLOCK_TIME_NONE;
792 :
793 9 : gst_buffer_list_insert(buffer_list, 0, GET_TYPE(INIT)->buffer);
794 : }
795 :
796 : // Add the header buffer if it's present
797 31 : if (header_present) {
798 30 : GST_DEBUG_OBJECT(ctx->sess->element, "Adding header buffer after init");
799 :
800 : // Set the flags
801 30 : GST_BUFFER_FLAG_SET(GET_TYPE(HEADER)->buffer, GST_BUFFER_FLAG_HEADER);
802 :
803 : // Set the delta unit based on the first sample
804 30 : if (!segment_boundary)
805 18 : GST_BUFFER_FLAG_SET(GET_TYPE(HEADER)->buffer, GST_BUFFER_FLAG_DELTA_UNIT);
806 :
807 : // Set the timing information
808 : // Set the timing information
809 30 : guint64 pts = G_MAXUINT64;
810 30 : guint64 dts = G_MAXUINT64;
811 30 : guint64 duration = 0;
812 30 : if (has_sample_info) {
813 : // Set PTS and DTS to the minimum of the samples
814 1069 : for (guint s = 0; s < mp4mx_ctx->next_samples->len; s++) {
815 1040 : SampleInfo* sample =
816 1040 : &g_array_index(mp4mx_ctx->next_samples, SampleInfo, s);
817 :
818 1040 : pts = MIN(pts, sample->pts);
819 1040 : dts = MIN(dts, sample->dts);
820 1040 : duration += sample->duration;
821 : }
822 : } else {
823 : // Set the PTS and DTS to the minimum of the data
824 1 : pts = GST_BUFFER_PTS(GET_TYPE(DATA)->buffer);
825 1 : dts = GST_BUFFER_DTS(GET_TYPE(DATA)->buffer);
826 1 : duration = GST_BUFFER_DURATION(GET_TYPE(DATA)->buffer);
827 : }
828 :
829 30 : GST_BUFFER_PTS(GET_TYPE(HEADER)->buffer) = pts;
830 30 : GST_BUFFER_DTS(GET_TYPE(HEADER)->buffer) = dts;
831 30 : GST_BUFFER_DURATION(GET_TYPE(HEADER)->buffer) = duration;
832 :
833 : // Append the mdat header
834 30 : if (mdat_hdr)
835 29 : gst_buffer_append_memory(GET_TYPE(HEADER)->buffer, mdat_hdr);
836 :
837 : // Insert the header buffer
838 30 : gst_buffer_list_insert(
839 30 : buffer_list, init_present ? 1 : 0, GET_TYPE(HEADER)->buffer);
840 : }
841 :
842 : // Reset the buffer contents
843 124 : for (guint i = 0; i < LAST; i++) {
844 93 : GET_TYPE(i)->buffer = NULL;
845 93 : GET_TYPE(i)->is_complete = FALSE;
846 : }
847 :
848 31 : return buffer_list;
849 : }
850 :
851 : BufferType
852 88 : mp4mx_get_buffer_type(GF_FilterPid* pid, guint32 box_type)
853 : {
854 : GPAC_MemOutPIDContext* ctx =
855 88 : (GPAC_MemOutPIDContext*)gf_filter_pid_get_udta(pid);
856 88 : Mp4mxCtx* mp4mx_ctx = (Mp4mxCtx*)ctx->private_ctx;
857 :
858 : // Check if the box type is related to the current type
859 150 : for (guint i = mp4mx_ctx->current_type; i < LAST; i++) {
860 423 : for (guint j = 0; box_mapping[i].boxes[j]; j++) {
861 361 : if (box_mapping[i].boxes[j] == box_type)
862 88 : return box_mapping[i].type;
863 : }
864 : }
865 :
866 0 : return LAST;
867 : }
868 :
869 : GF_Err
870 134 : mp4mx_post_process(GF_Filter* filter, GF_FilterPid* pid, GF_FilterPacket* pck)
871 : {
872 134 : GPAC_MemIoContext* ctx = (GPAC_MemIoContext*)gf_filter_get_rt_udta(filter);
873 : GPAC_MemOutPIDContext* pctx =
874 134 : (GPAC_MemOutPIDContext*)gf_filter_pid_get_udta(pid);
875 134 : Mp4mxCtx* mp4mx_ctx = (Mp4mxCtx*)pctx->private_ctx;
876 134 : if (!pck)
877 37 : return GF_OK;
878 :
879 : // Parse the boxes
880 97 : if (!mp4mx_parse_boxes(filter, pid, pck))
881 26 : return GF_OK;
882 :
883 : // Iterate over the boxes
884 159 : while (!g_queue_is_empty(mp4mx_ctx->box_queue)) {
885 89 : BoxInfo* box = g_queue_peek_head(mp4mx_ctx->box_queue);
886 89 : if (!mp4mx_is_box_complete(box))
887 1 : break;
888 :
889 88 : GST_DEBUG_OBJECT(
890 : ctx->sess->element, "Current type: %d", mp4mx_ctx->current_type);
891 :
892 : // Get the buffer type
893 88 : BufferType type = mp4mx_get_buffer_type(pid, box->box_type);
894 88 : if (type == LAST) {
895 0 : GST_WARNING_OBJECT(ctx->sess->element,
896 : "Box %s is not related to any current or future "
897 : "buffer types",
898 : gf_4cc_to_str(box->box_type));
899 0 : goto skip;
900 : }
901 :
902 88 : GST_DEBUG_OBJECT(ctx->sess->element,
903 : "Found box type %s for type %d",
904 : gf_4cc_to_str(box->box_type),
905 : type);
906 :
907 : // Mark all previous types as complete
908 150 : for (guint j = mp4mx_ctx->current_type; j < type; j++)
909 62 : GET_TYPE(j)->is_complete = TRUE;
910 :
911 : // Mark the current type as complete if it's DATA
912 88 : if (type == DATA)
913 31 : GET_TYPE(type)->is_complete = TRUE;
914 :
915 : // Set the current type
916 88 : mp4mx_ctx->current_type = type;
917 :
918 : // Create the master buffer
919 88 : GstBuffer** master_buffer = &GET_TYPE(type)->buffer;
920 88 : if (!(*master_buffer))
921 71 : *master_buffer = box->buffer;
922 : else
923 17 : *master_buffer = gst_buffer_append(*master_buffer, box->buffer);
924 :
925 88 : GST_DEBUG_OBJECT(ctx->sess->element,
926 : "New buffer [type: %d, size: %" G_GUINT32_FORMAT
927 : "]: %p (PTS: %" G_GUINT64_FORMAT
928 : ", DTS: %" G_GUINT64_FORMAT
929 : ", duration: %" G_GUINT64_FORMAT ")",
930 : type,
931 : box->box_size,
932 : *master_buffer,
933 : GST_BUFFER_PTS(*master_buffer),
934 : GST_BUFFER_DTS(*master_buffer),
935 : GST_BUFFER_DURATION(*master_buffer));
936 :
937 88 : skip:
938 : // Pop the box
939 88 : g_free(g_queue_pop_head(mp4mx_ctx->box_queue));
940 : }
941 :
942 : // Check if the fragment is completed
943 71 : if (!GET_TYPE(HEADER)->is_complete || !GET_TYPE(DATA)->is_complete)
944 40 : return GF_OK;
945 :
946 : // Create and enqueue the buffer list
947 31 : GstBufferList* buffer_list = mp4mx_create_buffer_list(filter, pid);
948 31 : g_queue_push_tail(mp4mx_ctx->output_queue, buffer_list);
949 :
950 : // Increment the segment count
951 31 : mp4mx_ctx->segment_count++;
952 31 : GST_DEBUG_OBJECT(ctx->sess->element,
953 : "Enqueued fragment #%" G_GUINT32_FORMAT,
954 : mp4mx_ctx->segment_count);
955 :
956 : // Reset the current type
957 31 : mp4mx_ctx->current_type = INIT;
958 :
959 31 : return GF_OK;
960 : }
961 :
962 : GPAC_FilterPPRet
963 918 : mp4mx_consume(GF_Filter* filter, GF_FilterPid* pid, void** outptr)
964 : {
965 : GPAC_MemOutPIDContext* ctx =
966 918 : (GPAC_MemOutPIDContext*)gf_filter_pid_get_udta(pid);
967 918 : Mp4mxCtx* mp4mx_ctx = (Mp4mxCtx*)ctx->private_ctx;
968 :
969 : // Check if the queue is empty
970 918 : if (g_queue_is_empty(mp4mx_ctx->output_queue))
971 888 : return GPAC_FILTER_PP_RET_EMPTY;
972 :
973 : // Assign the output
974 30 : if (outptr) {
975 30 : *outptr = g_queue_pop_head(mp4mx_ctx->output_queue);
976 30 : return GPAC_FILTER_PP_RET_BUFFER_LIST;
977 : }
978 0 : return GPAC_FILTER_PP_RET_NULL;
979 : }
|