int main(int argc, char **argv) { GF_Err e; VariantPlaylist * pl = NULL; char *url = argv[1]; //char *cache_m3u8_file; u32 i, count; FILE *fmpd; Bool verbose = 0; u32 update_interval = 0; char *m3u8_local_name = "file.m3u8"; Bool is_local = 0; gf_sys_init(0); gf_log_set_tool_level(GF_LOG_NETWORK, verbose ? GF_LOG_DEBUG : GF_LOG_INFO); while (1) { if (gf_url_is_local(url)) { m3u8_local_name = url; is_local = 1; } else { e = gf_dm_wget(url, m3u8_local_name, 0, 0); if (e != GF_OK) return -1; } e = parse_root_playlist(m3u8_local_name, &pl, "."); if (e != GF_OK) return -1; fmpd = fopen(argv[2], "wt"); fprintf(fmpd, "<MPD type=\"Live\" xmlns=\"urn:3GPP:ns:PSS:AdaptiveHTTPStreamingMPD:2009\">\n"); fprintf(fmpd, " <ProgramInformation moreInformationURL=\"http://gpac.sourceforge.net\">\n"); fprintf(fmpd, " <Title>Media Presentation Description for file %s</Title>\n", url); fprintf(fmpd, " <Source>Generated by GPAC %s</Source>\n", GPAC_FULL_VERSION); fprintf(fmpd, " </ProgramInformation>\n"); fprintf(fmpd, " <Period start=\"PT0S\">\n"); count = gf_list_count(pl->programs); for (i=0; i<count; i++) { u32 j, count2; Program *prog = gf_list_get(pl->programs, i); count2 = gf_list_count(prog->bitrates); for (j = 0; j<count2; j++) { PlaylistElement *pe = gf_list_get(prog->bitrates, j); fprintf(stdout, "%d, %d, %s, %s, %d\n", pe->durationInfo, pe->bandwidth, pe->title, pe->url, pe->elementType); if (pe->elementType == TYPE_PLAYLIST) { u32 k, count3; char *tmp; char c; char baseURL[GF_MAX_PATH]; tmp = strrchr(url, '/'); if (tmp) { tmp++; c = tmp[0]; tmp[0] = 0; strcpy(baseURL, url); tmp[0] = c; } else { baseURL[0] = 0; } fprintf(fmpd, " <Representation mimeType=\"video/mp2t\">\n"); fprintf(fmpd, " <SegmentInfo duration=\"PT%dS\"", pe->durationInfo); if (baseURL[0]) fprintf(fmpd, "baseURL=\"%s\"", baseURL); fprintf(fmpd, ">\n"); count3 = gf_list_count(pe->element.playlist.elements); update_interval = (count3 - 1) * pe->durationInfo * 1000; for (k=0; k<count3; k++) { PlaylistElement *elt = gf_list_get(pe->element.playlist.elements, k); if (k) fprintf(fmpd, " <Url sourceURL=\"%s\"/>\n", elt->url); else fprintf(fmpd, " <InitialisationSegmentURL sourceURL=\"%s\"/>\n", elt->url); } fprintf(fmpd, " </SegmentInfo>\n"); fprintf(fmpd, " </Representation>\n"); } else if (pe->elementType == TYPE_STREAM) { fprintf(stdout, "Stream\n"); } } } fprintf(fmpd, " </Period>\n"); fprintf(fmpd, "</MPD>"); fclose(fmpd); variant_playlist_del(pl); if (is_local) break; gf_sleep(update_interval); } gf_sys_close(); return 0; }
GF_Err parse_sub_playlist(const char * file, VariantPlaylist ** playlist, const char * baseURL, Program * in_program, PlaylistElement *sub_playlist) { int len, i, currentLineNumber; FILE * f=NULL; char *m3u8_payload; u32 m3u8_size, m3u8pos; VariantPlaylist * pl; char currentLine[M3U8_BUF_SIZE]; char ** attributes = NULL; s_accumulated_attributes attribs; if (!strncmp(file, "gmem://", 7)) { if (sscanf(file, "gmem://%d@%p", &m3u8_size, &m3u8_payload) != 2) { GF_LOG(GF_LOG_ERROR, GF_LOG_DASH,("[M3U8] Cannot Open m3u8 source %s for reading\n", file)); return GF_SERVICE_ERROR; } } else { f = gf_f64_open(file, "rt"); if (!f) { GF_LOG(GF_LOG_ERROR, GF_LOG_DASH,("[M3U8] Cannot Open m3u8 file %s for reading\n", file)); return GF_SERVICE_ERROR; } } if (*playlist == NULL) { *playlist = variant_playlist_new(); if (!(*playlist)) { if (f) fclose(f); return GF_OUT_OF_MEM; } } pl = *playlist; currentLineNumber = 0; bzero(&attribs, sizeof(s_accumulated_attributes)); attribs.bandwidth = 0; attribs.durationInSeconds = 0; attribs.targetDurationInSeconds = 0; attribs.isVariantPlaylist = 0; attribs.isPlaylistEnded = 0; attribs.minMediaSequence = 0; attribs.currentMediaSequence = 0; m3u8pos=0; while (1) { char * eof; if (f) { if (!fgets(currentLine, sizeof(currentLine), f)) break; } else { u32 __idx=0; if (m3u8pos>=m3u8_size) break; while (1) { currentLine[__idx] = m3u8_payload[m3u8pos]; __idx++; m3u8pos++; if ((currentLine[__idx-1]=='\n') || (currentLine[__idx-1]=='\r')) { currentLine[__idx]=0; break; } } } currentLineNumber++; eof = strchr(currentLine, '\r'); if (eof) eof[0] = '\0'; eof = strchr(currentLine, '\n'); if (eof) eof[0] = '\0'; len = strlen( currentLine); if (len < 1) continue; if (currentLineNumber == 1) { /* Playlist MUST start with #EXTM3U */ /* if (len < 7 || strncmp("#EXTM3U", currentLine, 7)!=0) { fclose(f); variant_playlist_del(pl); GF_LOG(GF_LOG_ERROR, GF_LOG_DASH, ("Failed to parse M3U8 File, it should start with #EXTM3U, but was : %s\n", currentLine)); return GF_STREAM_NOT_FOUND; } continue; */ } if (currentLine[0] == '#') { /* A comment or a directive */ if (strncmp("#EXT", currentLine, 4)==0) { attributes = parseAttributes(currentLine, &attribs); if (attributes == NULL) { MYLOG(("Comment at line %d : %s\n", currentLineNumber, currentLine)); } else { MYLOG(("Directive at line %d: \"%s\", attributes=", currentLineNumber, currentLine)); i = 0; while (attributes[i] != NULL) { MYLOG((" [%d]='%s'", i, attributes[i])); gf_free(attributes[i]); attributes[i] = NULL; i++; } MYLOG(("\n")); gf_free(attributes); attributes = NULL; } if (attribs.isPlaylistEnded) { pl->playlistNeedsRefresh = 0; } } } else { char * fullURL = currentLine; if (gf_url_is_local(currentLine)) { /* if (gf_url_is_local(baseURL)){ int num_chars = -1; if (baseURL[strlen(baseURL)-1] == '/'){ num_chars = asprintf(&fullURL, "%s%s", baseURL, currentLine); } else { num_chars = asprintf(&fullURL, "%s/%s", baseURL, currentLine); } if (num_chars < 0 || fullURL == NULL){ variant_playlist_del(*playlist); playlist = NULL; return GF_OUT_OF_MEM; } } else */ { fullURL = gf_url_concatenate(baseURL, currentLine); } assert( fullURL ); } { u32 count; PlaylistElement * currentPlayList = sub_playlist; /* First, we have to find the matching program */ Program * program = in_program; if (!in_program) program = variant_playlist_find_matching_program(pl, attribs.programId); /* We did not found the program, we create it */ if (program == NULL) { program = program_new(attribs.programId); if (program == NULL) { /* OUT of memory */ variant_playlist_del(*playlist); if (f) fclose(f); playlist = NULL; return GF_OUT_OF_MEM; } gf_list_add(pl->programs, program); if (pl->currentProgram < 0) pl->currentProgram = program->programId; } /* OK, we have a program, we have to choose the elements with same bandwidth */ assert( program ); assert( program->bitrates); count = gf_list_count( program->bitrates); if (!currentPlayList) { for (i = 0; i < (s32) count; i++) { PlaylistElement * itPlayListElement = gf_list_get(program->bitrates, i); assert( itPlayListElement ); if (itPlayListElement->bandwidth == attribs.bandwidth) { currentPlayList = itPlayListElement; break; } } } if (attribs.isVariantPlaylist) { /* We are the Variant Playlist */ if (currentPlayList != NULL) { /* should not happen, it means we redefine something previsouly added */ //assert( 0 ); } currentPlayList = playlist_element_new( TYPE_UNKNOWN, fullURL, attribs.title, attribs.codecs, attribs.durationInSeconds, attribs.byteRangeStart, attribs.byteRangeEnd); if (currentPlayList == NULL) { /* OUT of memory */ variant_playlist_del(*playlist); playlist = NULL; if (f) fclose(f); return GF_OUT_OF_MEM; } assert( fullURL); currentPlayList->url = gf_strdup(fullURL); currentPlayList->title = attribs.title ? gf_strdup(attribs.title):NULL; currentPlayList->codecs = attribs.codecs ? gf_strdup(attribs.codecs):NULL; gf_list_add(program->bitrates, currentPlayList); currentPlayList->width = attribs.width; currentPlayList->height = attribs.height; } else { /* Normal Playlist */ assert( pl->programs); if (currentPlayList == NULL) { /* This is in facts a "normal" playlist without any element in it */ PlaylistElement * subElement; assert(baseURL); currentPlayList = playlist_element_new( TYPE_PLAYLIST, baseURL, attribs.title, attribs.codecs, attribs.durationInSeconds, attribs.byteRangeStart, attribs.byteRangeEnd); if (currentPlayList == NULL) { /* OUT of memory */ variant_playlist_del(*playlist); playlist = NULL; if (f) fclose(f); return GF_OUT_OF_MEM; } assert(currentPlayList->element.playlist.elements); assert( fullURL); assert( currentPlayList->url); currentPlayList->title = NULL; currentPlayList->codecs = NULL; subElement = playlist_element_new( TYPE_UNKNOWN, fullURL, attribs.title, attribs.codecs, attribs.durationInSeconds, attribs.byteRangeStart, attribs.byteRangeEnd); if (subElement == NULL) { variant_playlist_del(*playlist); playlist_element_del(currentPlayList); playlist = NULL; if (f) fclose(f); return GF_OUT_OF_MEM; } gf_list_add(currentPlayList->element.playlist.elements, subElement); gf_list_add(program->bitrates, currentPlayList); currentPlayList->element.playlist.computed_duration += subElement->durationInfo; assert( program ); assert( program->bitrates); assert( currentPlayList); } else { PlaylistElement * subElement = playlist_element_new( TYPE_UNKNOWN, fullURL, attribs.title, attribs.codecs, attribs.durationInSeconds, attribs.byteRangeStart, attribs.byteRangeEnd); if (currentPlayList->elementType != TYPE_PLAYLIST) { currentPlayList->elementType = TYPE_PLAYLIST; if (!currentPlayList->element.playlist.elements) currentPlayList->element.playlist.elements = gf_list_new(); } if (subElement == NULL) { variant_playlist_del(*playlist); playlist_element_del(currentPlayList); playlist = NULL; if (f) fclose(f); return GF_OUT_OF_MEM; } gf_list_add(currentPlayList->element.playlist.elements, subElement); currentPlayList->element.playlist.computed_duration += subElement->durationInfo; } } currentPlayList->element.playlist.currentMediaSequence = attribs.currentMediaSequence ; /* We first set the default duration for element, aka targetDuration */ if (attribs.targetDurationInSeconds > 0) { currentPlayList->element.playlist.target_duration = attribs.targetDurationInSeconds; currentPlayList->durationInfo = attribs.targetDurationInSeconds; } if (attribs.durationInSeconds) { if (currentPlayList->durationInfo == 0) { /* we set the playlist duration info as the duration of a segment, only if it's not set There are cases of playlist with the last segment with a duration different from the others (example: Apple bipbop test)*/ currentPlayList->durationInfo = attribs.durationInSeconds; } } currentPlayList->element.playlist.mediaSequenceMin = attribs.minMediaSequence; currentPlayList->element.playlist.mediaSequenceMax = attribs.currentMediaSequence++; if (attribs.bandwidth > 1) currentPlayList->bandwidth = attribs.bandwidth; if (attribs.isPlaylistEnded) currentPlayList->element.playlist.is_ended = 1; } /* Cleanup all line-specific fields */ if (attribs.title) { gf_free(attribs.title); attribs.title = NULL; } attribs.durationInSeconds = 0; attribs.bandwidth = 0; attribs.programId = 0; if (attribs.codecs != NULL) { gf_free(attribs.codecs); attribs.codecs = NULL; } if (fullURL != currentLine) { gf_free(fullURL); } } } if (f) fclose(f); for (i=0; i < (int) gf_list_count(pl->programs); i++) { u32 j; Program *prog = gf_list_get(pl->programs, i); prog->computed_duration = 0; for (j=0; j<gf_list_count(prog->bitrates); j++) { PlaylistElement *ple = gf_list_get(prog->bitrates, j); if (ple->elementType==TYPE_PLAYLIST) { if (ple->element.playlist.computed_duration > prog->computed_duration) prog->computed_duration = ple->element.playlist.computed_duration; } } } return GF_OK; }
GF_Err parse_sub_playlist(const char * file, VariantPlaylist ** playlist, const char * baseURL, Program * in_program, PlaylistElement *sub_playlist) { int readen, readPointer, len, i, currentLineNumber; FILE * f; VariantPlaylist * pl; char currentLine[M3U8_BUF_SIZE]; char ** attributes = NULL; s_accumulated_attributes attribs; f = gf_f64_open(file, "rt"); if (!f) { GF_LOG( GF_LOG_ERROR, GF_LOG_CONTAINER,("[M3U8] Cannot Open m3u8 file %s for reading\n", file)); return GF_SERVICE_ERROR; } if (*playlist == NULL) { *playlist = variant_playlist_new(); if (!(*playlist)) { fclose(f); return GF_OUT_OF_MEM; } } pl = *playlist; readen=0; readPointer = 0; currentLineNumber = 0; bzero(&attribs, sizeof(s_accumulated_attributes)); attribs.bandwidth = 0; attribs.durationInSeconds = 0; attribs.targetDurationInSeconds = 0; attribs.isVariantPlaylist = 0; attribs.isPlaylistEnded = 0; attribs.minMediaSequence = 0; attribs.currentMediaSequence = 0; while (fgets(currentLine, sizeof(currentLine), f)) { char * eof; currentLineNumber++; eof = strchr(currentLine, '\r'); if (eof) eof[0] = '\0'; eof = strchr(currentLine, '\n'); if (eof) eof[0] = '\0'; len = strlen( currentLine); if (len < 1) continue; if (currentLineNumber == 1) { /* Playlist MUST start with #EXTM3U */ if (len < 7 || strncmp("#EXTM3U", currentLine, 7)!=0) { fclose(f); variant_playlist_del(pl); GF_LOG( GF_LOG_ERROR, GF_LOG_CONTAINER, ("Failed to parse M3U8 File, it should start with #EXTM3U, but was : %s\n", currentLine)); return GF_STREAM_NOT_FOUND; } continue; } if (currentLine[0] == '#') { /* A comment or a directive */ if (strncmp("#EXT", currentLine, 4)==0) { attributes = parseAttributes(currentLine, &attribs); if (attributes == NULL) { MYLOG(("Comment at line %d : %s\n", currentLineNumber, currentLine)); } else { MYLOG(("Directive at line %d: \"%s\", attributes=", currentLineNumber, currentLine)); i = 0; while (attributes[i] != NULL) { MYLOG((" [%d]='%s'", i, attributes[i])); gf_free(attributes[i]); attributes[i] = NULL; i++; } MYLOG(("\n")); gf_free(attributes); attributes = NULL; } if (attribs.isPlaylistEnded) { pl->playlistNeedsRefresh = 0; } } } else { char * fullURL = currentLine; //printf("Line %d: '%s'\n", currentLineNumber, currentLine); if (gf_url_is_local(currentLine)) { /* if (gf_url_is_local(baseURL)){ int num_chars = -1; if (baseURL[strlen(baseURL)-1] == '/'){ num_chars = asprintf(&fullURL, "%s%s", baseURL, currentLine); } else { num_chars = asprintf(&fullURL, "%s/%s", baseURL, currentLine); } if (num_chars < 0 || fullURL == NULL){ variant_playlist_del(*playlist); playlist = NULL; return GF_OUT_OF_MEM; } } else */ { fullURL = gf_url_concatenate(baseURL, currentLine); } assert( fullURL ); /*printf("*** calculated full path = %s from %s and %s\n", fullURL, currentLine, baseURL);*/ } { u32 count; PlaylistElement * currentPlayList = sub_playlist; /* First, we have to find the matching program */ Program * program = in_program; if (!in_program) program = variant_playlist_find_matching_program(pl, attribs.programId); /* We did not found the program, we create it */ if (program == NULL) { program = program_new(attribs.programId); if (program == NULL) { /* OUT of memory */ variant_playlist_del(*playlist); fclose(f); playlist = NULL; return GF_OUT_OF_MEM; } gf_list_add(pl->programs, program); if (pl->currentProgram < 0) pl->currentProgram = program->programId; } /* OK, we have a program, we have to choose the elements with same bandwidth */ assert( program ); assert( program->bitrates); count = gf_list_count( program->bitrates); if (!currentPlayList) { for (i = 0; i < (s32) count; i++) { PlaylistElement * itPlayListElement = gf_list_get(program->bitrates, i); assert( itPlayListElement ); if (itPlayListElement->bandwidth == attribs.bandwidth) { currentPlayList = itPlayListElement; break; } } } if (attribs.isVariantPlaylist) { /* We are the Variant Playlist */ if (currentPlayList != NULL) { /* should not happen, it means we redefine something previsouly added */ //assert( 0 ); } currentPlayList = playlist_element_new( TYPE_UNKNOWN, fullURL, attribs.title, attribs.codecs, attribs.durationInSeconds); if (currentPlayList == NULL) { /* OUT of memory */ variant_playlist_del(*playlist); playlist = NULL; fclose(f); return GF_OUT_OF_MEM; } assert( fullURL); currentPlayList->url = gf_strdup(fullURL); currentPlayList->title = attribs.title ? gf_strdup(attribs.title):NULL; currentPlayList->codecs = attribs.codecs ? gf_strdup(attribs.codecs):NULL; gf_list_add(program->bitrates, currentPlayList); } else { /* Normal Playlist */ assert( pl->programs); if (currentPlayList == NULL) { /* This is in facts a "normal" playlist without any element in it */ PlaylistElement * subElement; assert(baseURL); currentPlayList = playlist_element_new( TYPE_PLAYLIST, baseURL, attribs.title, attribs.codecs, attribs.durationInSeconds); if (currentPlayList == NULL) { /* OUT of memory */ variant_playlist_del(*playlist); playlist = NULL; fclose(f); return GF_OUT_OF_MEM; } assert(currentPlayList->element.playlist.elements); assert( fullURL); assert( currentPlayList->url); currentPlayList->title = NULL; currentPlayList->codecs = NULL; subElement = playlist_element_new( TYPE_UNKNOWN, fullURL, attribs.title, attribs.codecs, attribs.durationInSeconds); if (subElement == NULL) { variant_playlist_del(*playlist); playlist_element_del(currentPlayList); playlist = NULL; fclose(f); return GF_OUT_OF_MEM; } gf_list_add(currentPlayList->element.playlist.elements, subElement); gf_list_add(program->bitrates, currentPlayList); assert( program ); assert( program->bitrates); assert( currentPlayList); } else { PlaylistElement * subElement = playlist_element_new( TYPE_UNKNOWN, fullURL, attribs.title, attribs.codecs, attribs.durationInSeconds); if (currentPlayList->elementType != TYPE_PLAYLIST) { currentPlayList->elementType = TYPE_PLAYLIST; if (!currentPlayList->element.playlist.elements) currentPlayList->element.playlist.elements = gf_list_new(); } if (subElement == NULL) { variant_playlist_del(*playlist); playlist_element_del(currentPlayList); playlist = NULL; fclose(f); return GF_OUT_OF_MEM; } gf_list_add(currentPlayList->element.playlist.elements, subElement); } } currentPlayList->element.playlist.currentMediaSequence = attribs.currentMediaSequence ; /* We first set the default duration for element, aka targetDuration */ if (attribs.targetDurationInSeconds > 0) { currentPlayList->element.playlist.target_duration = attribs.targetDurationInSeconds; currentPlayList->durationInfo = attribs.targetDurationInSeconds; } if (attribs.durationInSeconds) { currentPlayList->durationInfo = attribs.durationInSeconds; } currentPlayList->element.playlist.mediaSequenceMin = attribs.minMediaSequence; currentPlayList->element.playlist.mediaSequenceMax = attribs.currentMediaSequence++; if (attribs.bandwidth > 1) currentPlayList->bandwidth = attribs.bandwidth; if (attribs.isPlaylistEnded) currentPlayList->element.playlist.is_ended = 1; } /* Cleanup all line-specific fields */ if (attribs.title) { gf_free(attribs.title); attribs.title = NULL; } attribs.durationInSeconds = 0; attribs.bandwidth = 0; attribs.programId = 0; if (attribs.codecs != NULL) { gf_free(attribs.codecs); attribs.codecs = NULL; } if (fullURL != currentLine) { gf_free(fullURL); } } } fclose(f); return GF_OK; }