Skip to content

Commit

Permalink
updated isobmf doc for calback-based output and added example - cf #2708
Browse files Browse the repository at this point in the history
  • Loading branch information
jeanlf committed Jan 19, 2024
1 parent 4ec4721 commit 738c581
Show file tree
Hide file tree
Showing 7 changed files with 204 additions and 55 deletions.
138 changes: 138 additions & 0 deletions applications/testapps/mp4mux/main.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
/*
* GPAC - Multimedia Framework C SDK
*
* Authors: Jean Le Feuvre
* Copyright (c) Telecom Paris 2024
* All rights reserved
*
* This file is part of GPAC - test MP4 muxer
*
*/

#include <gpac/tools.h>
#include <gpac/isomedia.h>


static GF_Err _on_data(void *cbk, u8 *data, u32 block_size, void *cbk_data, u32 cbk_magic)
{
FILE *f = cbk;
gf_fwrite(data, block_size, f);
return GF_OK;
}
static GF_Err _on_data_patch(void *cbk, u8 *block, u32 block_size, u64 block_offset, Bool is_insert)
{
FILE *f = cbk;
u64 pos = gf_ftell(f);
gf_fseek(f, block_offset, SEEK_SET);
gf_fwrite(block, block_size, f);
gf_fseek(f, pos, SEEK_SET);
return GF_OK;
}

void mp4mux_api_test(u32 frag_type, Bool moov_first, const char *fileName)
{
GF_Err e;
//open target file
FILE *f = gf_fopen(fileName, "w");
//open movie
GF_ISOFile *nf = gf_isom_open("_gpac_isobmff_redirect", (!frag_type && moov_first) ? GF_ISOM_WRITE_EDIT : GF_ISOM_OPEN_WRITE, NULL);

//set storage mode to interlleaved
if (moov_first && !frag_type) {
e = gf_isom_set_storage_mode(nf, GF_ISOM_STORE_INTERLEAVED); //or GF_ISOM_STORE_STREAMABLE since we only have one track
assert(!e);
}
//setup our callbacks, we don't use the last_block callback
e = gf_isom_set_write_callback(nf, _on_data, _on_data_patch, NULL, f, 1000);
assert(!e);

//movie setup
e = gf_isom_set_brand_info(nf, GF_ISOM_BRAND_MP42, 0);
assert(!e);
e = gf_isom_set_timescale(nf, 1000);
assert(!e);
u32 track_id = gf_isom_new_track(nf, 0, GF_ISOM_MEDIA_SUBT, 1000);
assert(track_id);
e = gf_isom_set_track_enabled(nf, track_id, GF_TRUE);
assert(!e);
u32 i, di;
e = gf_isom_new_stxt_description(nf, 1, GF_ISOM_SUBTYPE_STXT, "text/text", NULL, NULL, &di);
assert(!e);

//setup fragmentation
if (frag_type) {
e = gf_isom_setup_track_fragment(nf, track_id, di, 2000, 0, 1, 0, 0, 0);
assert(!e);
//will write init segment
e = gf_isom_finalize_for_fragment(nf, (frag_type==2) ? 1 : 0, GF_TRUE);
assert(!e);
}
//add samples
u64 first_dts=0;
u64 dts=0;
u32 duration=2000;
for (i=0; i<10000; i++) {
char szTxt[100];
GF_ISOSample s;

if (frag_type && ((i%100)==0)) {
if (frag_type==2) {
if (i) {
e = gf_isom_close_segment(nf, 0, track_id, first_dts, 0, dts+duration, GF_FALSE, GF_FALSE, GF_FALSE, GF_FALSE, 0, NULL, NULL, NULL);
assert(!e);
}
//use either NULL if you store in same file or "_gpac_isobmff_redirect" for multiple files (to setup styp and indexing)
e = gf_isom_start_segment(nf, NULL, GF_FALSE);
assert(!e);
first_dts = dts;
//in this demo, one fragment per segment
}

//starting a fragment will flush all pending data
e = gf_isom_start_fragment(nf, GF_ISOM_FRAG_MOOF_FIRST);
assert(!e);
}

sprintf(szTxt, "Text sample #%d", i+1);
memset(&s, 0, sizeof(GF_ISOSample));
s.dataLength = (u32) strlen(szTxt);
s.data = (u8 *)szTxt;
s.DTS = dts;
s.IsRAP = 1;
if (frag_type) {
e = gf_isom_fragment_add_sample(nf, track_id, &s, di, 2000, 0, 0, GF_FALSE);
} else {
e = gf_isom_add_sample(nf, 1, di, &s);
}
assert(!e);
dts+=duration;
}
//close segment
if (frag_type==2) {
e = gf_isom_close_segment(nf, 0, track_id, first_dts, 0, dts+duration, GF_FALSE, GF_FALSE, GF_FALSE, GF_FALSE, 0, NULL, NULL, NULL);
assert(!e);
}

//flush all pending data and delete
e = gf_isom_close(nf);
assert(!e);
gf_fclose(f);
}

int main(int argc, char **argv)
{

gf_sys_init(0, "0");

//mux using callbacks, no fragments and mdat first
mp4mux_api_test(0, GF_FALSE, "mux_flat.mp4");
//mux using callbacks, no fragments and moov first
mp4mux_api_test(0, GF_TRUE, "mux_inter.mp4");
//mux using callbacks and fragments
mp4mux_api_test(1, GF_FALSE, "mux_frag.mp4");
//mux using callbacks and segments
mp4mux_api_test(2, GF_FALSE, "mux_seg.mp4");

gf_sys_close();
}

1 change: 1 addition & 0 deletions include/gpac/internal/isomedia_dev.h
Original file line number Diff line number Diff line change
Expand Up @@ -4228,6 +4228,7 @@ struct __tag_isom {
u64 fragmented_file_pos;
u8 *block_buffer;
u32 block_buffer_size;
Bool blocks_sent;

u32 nb_box_init_seg;

Expand Down
48 changes: 34 additions & 14 deletions include/gpac/isomedia.h
Original file line number Diff line number Diff line change
Expand Up @@ -665,15 +665,14 @@ u32 gf_isom_probe_file_range(const char *fileName, u64 start_range, u64 end_rang
u32 gf_isom_probe_data(const u8*inBuf, u32 inSize);

/*! opens an isoMedia File.
If fileName is NULL data will be written in memory ; write with gf_isom_write() ; use gf_isom_get_bs() to get the data ; use gf_isom_delete() to delete the internal data.
\param fileName name of the file to open, , gmem:// or gfio:// resource. The special name "_gpac_isobmff_redirect" is used to indicate that segment shall be written to a memory buffer passed to callback function set through \ref gf_isom_set_write_callback.
\param fileName name of the file to open, , gmem:// or gfio:// resource. The special name "_gpac_isobmff_redirect" is used to indicate that segment shall be written to a memory buffer passed to callback function set through \ref gf_isom_set_write_callback. SHALL not be NULL.
\param OpenMode file opening mode
\param tmp_dir for the 2 edit modes only, specifies a location for temp file. If NULL, the library will use the default libgpac temporary file management schemes.
\return the created ISO file if no error
*/
GF_ISOFile *gf_isom_open(const char *fileName, GF_ISOOpenMode OpenMode, const char *tmp_dir);

/*! closes the file, write it if new/edited - equivalent to gf_isom_write()+gf_isom_delete()
/*! closes the file, write it if new/edited or if pending fragment
\param isom_file the target ISO file
\return error if any
*/
Expand Down Expand Up @@ -1831,12 +1830,6 @@ typedef enum
GF_ISOM_STORE_FASTSTART,
} GF_ISOStorageMode;

/*! writes the file without deleting (see \ref gf_isom_delete)
\param isom_file the target ISO file
\return error if any
*/
GF_Err gf_isom_write(GF_ISOFile *isom_file);

/*! freezes order of the current box tree in the file.
By default the library always reorder boxes in the recommended order in the various specifications implemented.
New created tracks or meta items will not have a frozen order of boxes, but the function can be called several time
Expand Down Expand Up @@ -2833,19 +2826,46 @@ GF_Err gf_isom_make_interleave_ex(GF_ISOFile *isom_file, GF_Fraction *fTimeInSec
*/
void gf_isom_set_progress_callback(GF_ISOFile *isom_file, void (*progress_cbk)(void *udta, u64 nb_done, u64 nb_total), void *progress_cbk_udta);

/*! Callback function to receive new data blocks
\param usr_data user callback, as passed to \ref gf_isom_set_write_callback
\param block data block to write
\param block_size data block size in bytes
\param sample_cbk_data callback data of sample or NULL
\param sample_cbk_magic callback magic of sample or 0
\return error if any
*/
typedef GF_Err (*gf_isom_on_block_out)(void *usr_data, u8 *block, u32 block_size, void *sample_cbk_data, u32 sample_cbk_magic);

/*! Callback function to receive new data blocks, only used in non-fragmented mode:
- to patch mdat size
- to inject moov for GF_ISOM_STORE_FASTSTART mode
\param usr_data user callback, as passed to \ref gf_isom_set_write_callback
\param block data block to write
\param block_size data block size in bytes
\param block_offset offset in file for block to patch
\param is_insert if GF_TRUE, indicates the bytes must be inserted at the given offset. Otherwise bytes are to be replaced
\return error if any
*/
typedef GF_Err (*gf_isom_on_block_patch)(void *usr_data, u8 *block, u32 block_size, u64 block_offset, Bool is_insert);

/*! Callback function to indicate the last call to \ref gf_isom_on_block_out is about to be produced for a segment, unused for non-fragmented or non-dash cases
\param usr_data user callback, as passed to \ref gf_isom_set_write_callback
*/
typedef void (*gf_isom_on_last_block_start)(void *usr_data);

/*! sets write callback functions for in-memory file writing
\param isom_file the target ISO file
\param on_block_out the block write callback function
\param on_block_patch the block patch callback function
\param on_block_out the block write callback function, mandatory
\param on_block_patch the block patch callback function, may be NULL if only fragmented files or very small files are being produced
\param on_last_block_start called before writing the last block of a sequence of movie fragments
\param usr_data opaque user data passed to callback functions
\param block_size desired block size in bytes
\return error if any
*/
GF_Err gf_isom_set_write_callback(GF_ISOFile *isom_file,
GF_Err (*on_block_out)(void *cbk, u8 *data, u32 block_size, void *cbk_data, u32 cbk_magic),
GF_Err (*on_block_patch)(void *usr_data, u8 *block, u32 block_size, u64 block_offset, Bool is_insert),
void (*on_last_block_start)(void *cbk),
gf_isom_on_block_out on_block_out,
gf_isom_on_block_patch on_block_patch,
gf_isom_on_last_block_start on_last_block_start,
void *usr_data,
u32 block_size);

Expand Down
13 changes: 7 additions & 6 deletions src/isomedia/isom_intern.c
Original file line number Diff line number Diff line change
Expand Up @@ -1043,16 +1043,17 @@ GF_ISOFile *gf_isom_open_file(const char *fileName, GF_ISOOpenMode OpenMode, con
}

GF_Err gf_isom_set_write_callback(GF_ISOFile *mov,
GF_Err (*on_block_out)(void *cbk, u8 *data, u32 block_size, void *cbk_data, u32 cbk_magic),
GF_Err (*on_block_patch)(void *usr_data, u8 *block, u32 block_size, u64 block_offset, Bool is_insert),
void (*on_last_block_start)(void *usr_data),
gf_isom_on_block_out on_block_out,
gf_isom_on_block_patch on_block_patch,
gf_isom_on_last_block_start on_last_block_start,
void *usr_data,
u32 block_size)
{
#ifndef GPAC_DISABLE_ISOM_WRITE
if (mov->finalName && !strcmp(mov->finalName, "_gpac_isobmff_redirect")) {}
else if (mov->fileName && !strcmp(mov->fileName, "_gpac_isobmff_redirect")) {}
else return GF_BAD_PARAM;
if (!on_block_out) return GF_BAD_PARAM;
mov->on_block_out = on_block_out;
mov->on_block_patch = on_block_patch;
mov->on_last_block_start = on_last_block_start;
Expand Down Expand Up @@ -1410,7 +1411,7 @@ GF_ISOFile *gf_isom_create_movie(const char *fileName, GF_ISOOpenMode OpenMode,
GF_Err e;

GF_ISOFile *mov = gf_isom_new_movie();
if (!mov) return NULL;
if (!mov || !fileName) return NULL;
mov->openMode = OpenMode;
//then set up our movie

Expand All @@ -1422,7 +1423,7 @@ GF_ISOFile *gf_isom_create_movie(const char *fileName, GF_ISOOpenMode OpenMode,
const char *ext;
//THIS IS NOT A TEMP FILE, WRITE mode is used for "live capture"
//this file will be the final file...
mov->fileName = fileName ? gf_strdup(fileName) : NULL;
mov->fileName = gf_strdup(fileName);
e = gf_isom_datamap_new(fileName, NULL, GF_ISOM_DATA_MAP_WRITE, &mov->editFileMap);
if (e) goto err_exit;

Expand All @@ -1435,7 +1436,7 @@ GF_ISOFile *gf_isom_create_movie(const char *fileName, GF_ISOOpenMode OpenMode,
}
} else {
//we are in EDIT mode but we are creating the file -> temp file
mov->finalName = fileName ? gf_strdup(fileName) : NULL;
mov->finalName = gf_strdup(fileName);
e = gf_isom_datamap_new("_gpac_isobmff_tmp_edit", tmp_dir, GF_ISOM_DATA_MAP_WRITE, &mov->editFileMap);
if (e) {
gf_isom_set_last_error(NULL, e);
Expand Down
32 changes: 1 addition & 31 deletions src/isomedia/isom_read.c
Original file line number Diff line number Diff line change
Expand Up @@ -553,37 +553,7 @@ GF_ISOFile *gf_isom_open(const char *fileName, GF_ISOOpenMode OpenMode, const ch
return (GF_ISOFile *) movie;
}


#if 0
/*! gets access to the data bitstream - see \ref gf_isom_open
\param isom_file the target ISO file
\param out_bs set to the file input bitstream - do NOT destroy
\return error if any
*/
GF_Err gf_isom_get_bs(GF_ISOFile *movie, GF_BitStream **out_bs)
{
#ifndef GPAC_DISABLE_ISOM_WRITE
if (!movie || movie->openMode != GF_ISOM_OPEN_WRITE || !movie->editFileMap) //memory mode
return GF_NOT_SUPPORTED;

if (movie->segment_bs)
*out_bs = movie->segment_bs;
else
*out_bs = movie->editFileMap->bs;

if (movie->moof)
movie->moof->fragment_offset = 0;

return GF_OK;
#else
return GF_NOT_SUPPORTED;
#endif
}
#endif


GF_EXPORT
GF_Err gf_isom_write(GF_ISOFile *movie)
static GF_Err gf_isom_write(GF_ISOFile *movie)
{
GF_Err e;
if (movie == NULL) return GF_ISOM_INVALID_FILE;
Expand Down
25 changes: 22 additions & 3 deletions src/isomedia/isom_store.c
Original file line number Diff line number Diff line change
Expand Up @@ -2481,6 +2481,7 @@ static GF_Err WriteInplace(MovieWriter *mw, GF_BitStream *bs)
GF_Err isom_on_block_out(void *cbk, u8 *data, u32 block_size)
{
GF_ISOFile *movie = (GF_ISOFile *)cbk;
movie->blocks_sent = GF_TRUE;
return movie->on_block_out(movie->on_block_out_usr_data, data, block_size, NULL, 0);
}

Expand Down Expand Up @@ -2556,11 +2557,12 @@ GF_Err WriteToFile(GF_ISOFile *movie, Bool for_fragments)
}
//capture mode: we don't need a new bitstream
if (movie->openMode == GF_ISOM_OPEN_WRITE) {
if (!strcmp(movie->fileName, "_gpac_isobmff_redirect")) {
if (movie->fileName && !strcmp(movie->fileName, "_gpac_isobmff_redirect")) {
GF_BitStream *bs, *moov_bs=NULL;
u64 mdat_end = gf_bs_get_position(movie->editFileMap->bs);
u64 mdat_start = movie->mdat->bsOffset;
u64 mdat_size = mdat_end - mdat_start;
Bool patch_mdat=GF_TRUE;

if (for_fragments) {
if (!movie->on_block_out) {
Expand All @@ -2585,14 +2587,27 @@ GF_Err WriteToFile(GF_ISOFile *movie, Bool for_fragments)
pad--;
}
}
if (!movie->blocks_sent && mdat_start && mdat_size) {
u64 pos = gf_bs_get_position(movie->editFileMap->bs);
gf_bs_seek(movie->editFileMap->bs, mdat_start);
gf_bs_write_u32(movie->editFileMap->bs, (mdat_size>0xFFFFFFFF) ? 1 : (u32) mdat_size);
gf_bs_write_u32(movie->editFileMap->bs, GF_ISOM_BOX_TYPE_MDAT);
if (mdat_size>0xFFFFFFFF)
gf_bs_write_u64(movie->editFileMap->bs, mdat_size);
else
gf_bs_write_u64(movie->editFileMap->bs, 0);

gf_bs_seek(movie->editFileMap->bs, pos);
patch_mdat=GF_FALSE;
}
//write as non-seekable
e = WriteFlat(&mw, 0, movie->editFileMap->bs, GF_TRUE, GF_FALSE, moov_bs);

movie->fragmented_file_pos = gf_bs_get_position(movie->editFileMap->bs);

if (mdat_start && mdat_size) {
if (mdat_start && mdat_size && patch_mdat) {
u8 data[16];
if (!movie->on_block_out) {
if (!movie->on_block_patch && movie->blocks_sent) {
GF_LOG(GF_LOG_ERROR, GF_LOG_CONTAINER, ("[ISOBMFF] Missing output block patch callback, cannot patch mdat size in flat storage\n"));
return GF_BAD_PARAM;
}
Expand All @@ -2612,6 +2627,10 @@ GF_Err WriteToFile(GF_ISOFile *movie, Bool for_fragments)
if (moov_bs) {
u8 *moov_data;
u32 moov_size;
if (!movie->on_block_patch) {
GF_LOG(GF_LOG_ERROR, GF_LOG_CONTAINER, ("[ISOBMFF] Missing output block patch callback, cannot patch mdat size in fast-start storage\n"));
return GF_BAD_PARAM;
}

gf_bs_get_content(moov_bs, &moov_data, &moov_size);
gf_bs_del(moov_bs);
Expand Down
2 changes: 1 addition & 1 deletion src/isomedia/isom_write.c
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ GF_Err FlushCaptureMode(GF_ISOFile *movie)
/*make sure nothing was added*/
if (gf_bs_get_position(movie->editFileMap->bs)) return GF_OK;

if (!strcmp(movie->fileName, "_gpac_isobmff_redirect")) {
if (movie->fileName && !strcmp(movie->fileName, "_gpac_isobmff_redirect")) {
if (!movie->on_block_out) {
GF_LOG(GF_LOG_ERROR, GF_LOG_CONTAINER, ("[ISOBMFF] Missing output block callback, cannot write\n"));
return GF_BAD_PARAM;
Expand Down

0 comments on commit 738c581

Please sign in to comment.