Spaces:
Runtime error
Runtime error
/* | |
* Copyright (c) 2014 Martin Storsjo | |
* | |
* This file is part of FFmpeg. | |
* | |
* FFmpeg is free software; you can redistribute it and/or | |
* modify it under the terms of the GNU Lesser General Public | |
* License as published by the Free Software Foundation; either | |
* version 2.1 of the License, or (at your option) any later version. | |
* | |
* FFmpeg is distributed in the hope that it will be useful, | |
* but WITHOUT ANY WARRANTY; without even the implied warranty of | |
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |
* Lesser General Public License for more details. | |
* | |
* You should have received a copy of the GNU Lesser General Public | |
* License along with FFmpeg; if not, write to the Free Software | |
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA | |
*/ | |
static int usage(const char *argv0, int ret) | |
{ | |
fprintf(stderr, "%s -out foo.mpd file1\n", argv0); | |
return ret; | |
} | |
struct Track { | |
const char *name; | |
int64_t duration; | |
int bitrate; | |
int track_id; | |
int is_audio, is_video; | |
int width, height; | |
int sample_rate, channels; | |
int timescale; | |
char codec_str[30]; | |
int64_t sidx_start, sidx_length; | |
}; | |
struct Tracks { | |
int nb_tracks; | |
int64_t duration; | |
struct Track **tracks; | |
int multiple_tracks_per_file; | |
}; | |
static void set_codec_str(AVCodecParameters *codecpar, char *str, int size) | |
{ | |
switch (codecpar->codec_id) { | |
case AV_CODEC_ID_H264: | |
snprintf(str, size, "avc1"); | |
if (codecpar->extradata_size >= 4 && codecpar->extradata[0] == 1) { | |
av_strlcatf(str, size, ".%02x%02x%02x", | |
codecpar->extradata[1], codecpar->extradata[2], | |
codecpar->extradata[3]); | |
} | |
break; | |
case AV_CODEC_ID_AAC: | |
snprintf(str, size, "mp4a.40"); // 0x40 is the mp4 object type for AAC | |
if (codecpar->extradata_size >= 2) { | |
int aot = codecpar->extradata[0] >> 3; | |
if (aot == 31) | |
aot = ((AV_RB16(codecpar->extradata) >> 5) & 0x3f) + 32; | |
av_strlcatf(str, size, ".%d", aot); | |
} | |
break; | |
} | |
} | |
static int find_sidx(struct Tracks *tracks, int start_index, | |
const char *file) | |
{ | |
int err = 0; | |
AVIOContext *f = NULL; | |
int i; | |
if ((err = avio_open2(&f, file, AVIO_FLAG_READ, NULL, NULL)) < 0) | |
goto fail; | |
while (!f->eof_reached) { | |
int64_t pos = avio_tell(f); | |
int32_t size, tag; | |
size = avio_rb32(f); | |
tag = avio_rb32(f); | |
if (size < 8) | |
break; | |
if (tag == MKBETAG('s', 'i', 'd', 'x')) { | |
for (i = start_index; i < tracks->nb_tracks; i++) { | |
struct Track *track = tracks->tracks[i]; | |
if (!track->sidx_start) { | |
track->sidx_start = pos; | |
track->sidx_length = size; | |
} else if (pos == track->sidx_start + track->sidx_length) { | |
track->sidx_length = pos + size - track->sidx_start; | |
} | |
} | |
} | |
if (avio_seek(f, pos + size, SEEK_SET) != pos + size) | |
break; | |
} | |
fail: | |
if (f) | |
avio_close(f); | |
return err; | |
} | |
static int handle_file(struct Tracks *tracks, const char *file) | |
{ | |
AVFormatContext *ctx = NULL; | |
int err = 0, i, orig_tracks = tracks->nb_tracks; | |
char errbuf[50], *ptr; | |
struct Track *track; | |
err = avformat_open_input(&ctx, file, NULL, NULL); | |
if (err < 0) { | |
av_strerror(err, errbuf, sizeof(errbuf)); | |
fprintf(stderr, "Unable to open %s: %s\n", file, errbuf); | |
return 1; | |
} | |
err = avformat_find_stream_info(ctx, NULL); | |
if (err < 0) { | |
av_strerror(err, errbuf, sizeof(errbuf)); | |
fprintf(stderr, "Unable to identify %s: %s\n", file, errbuf); | |
goto fail; | |
} | |
if (ctx->nb_streams < 1) { | |
fprintf(stderr, "No streams found in %s\n", file); | |
goto fail; | |
} | |
if (ctx->nb_streams > 1) | |
tracks->multiple_tracks_per_file = 1; | |
for (i = 0; i < ctx->nb_streams; i++) { | |
struct Track **temp; | |
AVStream *st = ctx->streams[i]; | |
if (st->codecpar->bit_rate == 0) { | |
fprintf(stderr, "Skipping track %d in %s as it has zero bitrate\n", | |
st->id, file); | |
continue; | |
} | |
track = av_mallocz(sizeof(*track)); | |
if (!track) { | |
err = AVERROR(ENOMEM); | |
goto fail; | |
} | |
temp = av_realloc_array(tracks->tracks, tracks->nb_tracks + 1, | |
sizeof(*tracks->tracks)); | |
if (!temp) { | |
av_free(track); | |
err = AVERROR(ENOMEM); | |
goto fail; | |
} | |
tracks->tracks = temp; | |
tracks->tracks[tracks->nb_tracks] = track; | |
track->name = file; | |
if ((ptr = strrchr(file, '/'))) | |
track->name = ptr + 1; | |
track->bitrate = st->codecpar->bit_rate; | |
track->track_id = st->id; | |
track->timescale = st->time_base.den; | |
track->duration = st->duration; | |
track->is_audio = st->codecpar->codec_type == AVMEDIA_TYPE_AUDIO; | |
track->is_video = st->codecpar->codec_type == AVMEDIA_TYPE_VIDEO; | |
if (!track->is_audio && !track->is_video) { | |
fprintf(stderr, | |
"Track %d in %s is neither video nor audio, skipping\n", | |
track->track_id, file); | |
av_freep(&tracks->tracks[tracks->nb_tracks]); | |
continue; | |
} | |
tracks->duration = FFMAX(tracks->duration, | |
av_rescale_rnd(track->duration, AV_TIME_BASE, | |
track->timescale, AV_ROUND_UP)); | |
if (track->is_audio) { | |
track->channels = st->codecpar->ch_layout.nb_channels; | |
track->sample_rate = st->codecpar->sample_rate; | |
} | |
if (track->is_video) { | |
track->width = st->codecpar->width; | |
track->height = st->codecpar->height; | |
} | |
set_codec_str(st->codecpar, track->codec_str, sizeof(track->codec_str)); | |
tracks->nb_tracks++; | |
} | |
avformat_close_input(&ctx); | |
err = find_sidx(tracks, orig_tracks, file); | |
fail: | |
if (ctx) | |
avformat_close_input(&ctx); | |
return err; | |
} | |
static void write_time(FILE *out, int64_t time, int decimals, enum AVRounding round) | |
{ | |
int seconds = time / AV_TIME_BASE; | |
int fractions = time % AV_TIME_BASE; | |
int minutes = seconds / 60; | |
int hours = minutes / 60; | |
fractions = av_rescale_rnd(fractions, pow(10, decimals), AV_TIME_BASE, round); | |
seconds %= 60; | |
minutes %= 60; | |
fprintf(out, "PT"); | |
if (hours) | |
fprintf(out, "%dH", hours); | |
if (hours || minutes) | |
fprintf(out, "%dM", minutes); | |
fprintf(out, "%d.%0*dS", seconds, decimals, fractions); | |
} | |
static int output_mpd(struct Tracks *tracks, const char *filename) | |
{ | |
FILE *out; | |
int i, j, ret = 0; | |
struct Track **adaptation_sets_buf[2] = { NULL }; | |
struct Track ***adaptation_sets; | |
int nb_tracks_buf[2] = { 0 }; | |
int *nb_tracks; | |
int set, nb_sets; | |
if (!tracks->multiple_tracks_per_file) { | |
adaptation_sets = adaptation_sets_buf; | |
nb_tracks = nb_tracks_buf; | |
nb_sets = 2; | |
for (i = 0; i < 2; i++) { | |
adaptation_sets[i] = av_malloc_array(tracks->nb_tracks, sizeof(*adaptation_sets[i])); | |
if (!adaptation_sets[i]) { | |
ret = AVERROR(ENOMEM); | |
goto err; | |
} | |
} | |
for (i = 0; i < tracks->nb_tracks; i++) { | |
int set_index = -1; | |
if (tracks->tracks[i]->is_video) | |
set_index = 0; | |
else if (tracks->tracks[i]->is_audio) | |
set_index = 1; | |
else | |
continue; | |
adaptation_sets[set_index][nb_tracks[set_index]++] = tracks->tracks[i]; | |
} | |
} else { | |
adaptation_sets = &tracks->tracks; | |
nb_tracks = &tracks->nb_tracks; | |
nb_sets = 1; | |
} | |
out = fopen(filename, "w"); | |
if (!out) { | |
ret = AVERROR(errno); | |
perror(filename); | |
return ret; | |
} | |
fprintf(out, "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"); | |
fprintf(out, "<MPD xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n" | |
"\txmlns=\"urn:mpeg:dash:schema:mpd:2011\"\n" | |
"\txmlns:xlink=\"http://www.w3.org/1999/xlink\"\n" | |
"\txsi:schemaLocation=\"urn:mpeg:DASH:schema:MPD:2011 http://standards.iso.org/ittf/PubliclyAvailableStandards/MPEG-DASH_schema_files/DASH-MPD.xsd\"\n" | |
"\tprofiles=\"urn:mpeg:dash:profile:isoff-on-demand:2011\"\n" | |
"\ttype=\"static\"\n"); | |
fprintf(out, "\tmediaPresentationDuration=\""); | |
write_time(out, tracks->duration, 1, AV_ROUND_DOWN); | |
fprintf(out, "\"\n"); | |
fprintf(out, "\tminBufferTime=\"PT5S\">\n"); | |
fprintf(out, "\t<Period start=\"PT0.0S\">\n"); | |
for (set = 0; set < nb_sets; set++) { | |
if (nb_tracks[set] == 0) | |
continue; | |
fprintf(out, "\t\t<AdaptationSet segmentAlignment=\"true\">\n"); | |
if (nb_sets == 1) { | |
for (i = 0; i < nb_tracks[set]; i++) { | |
struct Track *track = adaptation_sets[set][i]; | |
if (strcmp(track->name, adaptation_sets[set][0]->name)) | |
break; | |
fprintf(out, "\t\t\t<ContentComponent id=\"%d\" contentType=\"%s\" />\n", track->track_id, track->is_audio ? "audio" : "video"); | |
} | |
} | |
for (i = 0; i < nb_tracks[set]; ) { | |
struct Track *first_track = adaptation_sets[set][i]; | |
int width = 0, height = 0, sample_rate = 0, channels = 0, bitrate = 0; | |
fprintf(out, "\t\t\t<Representation id=\"%d\" codecs=\"", i); | |
for (j = i; j < nb_tracks[set]; j++) { | |
struct Track *track = adaptation_sets[set][j]; | |
if (strcmp(track->name, first_track->name)) | |
break; | |
if (track->is_audio) { | |
sample_rate = track->sample_rate; | |
channels = track->channels; | |
} | |
if (track->is_video) { | |
width = track->width; | |
height = track->height; | |
} | |
bitrate += track->bitrate; | |
if (j > i) | |
fprintf(out, ","); | |
fprintf(out, "%s", track->codec_str); | |
} | |
fprintf(out, "\" mimeType=\"%s/mp4\" bandwidth=\"%d\"", | |
width ? "video" : "audio", bitrate); | |
if (width > 0 && height > 0) | |
fprintf(out, " width=\"%d\" height=\"%d\"", width, height); | |
if (sample_rate > 0) | |
fprintf(out, " audioSamplingRate=\"%d\"", sample_rate); | |
fprintf(out, ">\n"); | |
if (channels > 0) | |
fprintf(out, "\t\t\t\t<AudioChannelConfiguration schemeIdUri=\"urn:mpeg:dash:23003:3:audio_channel_configuration:2011\" value=\"%d\" />\n", channels); | |
fprintf(out, "\t\t\t\t<BaseURL>%s</BaseURL>\n", first_track->name); | |
fprintf(out, "\t\t\t\t<SegmentBase indexRange=\"%"PRId64"-%"PRId64"\" />\n", first_track->sidx_start, first_track->sidx_start + first_track->sidx_length - 1); | |
fprintf(out, "\t\t\t</Representation>\n"); | |
i = j; | |
} | |
fprintf(out, "\t\t</AdaptationSet>\n"); | |
} | |
fprintf(out, "\t</Period>\n"); | |
fprintf(out, "</MPD>\n"); | |
fclose(out); | |
err: | |
for (i = 0; i < 2; i++) | |
av_free(adaptation_sets_buf[i]); | |
return ret; | |
} | |
static void clean_tracks(struct Tracks *tracks) | |
{ | |
int i; | |
for (i = 0; i < tracks->nb_tracks; i++) { | |
av_freep(&tracks->tracks[i]); | |
} | |
av_freep(&tracks->tracks); | |
tracks->nb_tracks = 0; | |
} | |
int main(int argc, char **argv) | |
{ | |
const char *out = NULL; | |
struct Tracks tracks = { 0 }; | |
int i; | |
for (i = 1; i < argc; i++) { | |
if (!strcmp(argv[i], "-out")) { | |
out = argv[i + 1]; | |
i++; | |
} else if (argv[i][0] == '-') { | |
return usage(argv[0], 1); | |
} else { | |
if (handle_file(&tracks, argv[i])) | |
return 1; | |
} | |
} | |
if (!tracks.nb_tracks || !out) | |
return usage(argv[0], 1); | |
output_mpd(&tracks, out); | |
clean_tracks(&tracks); | |
return 0; | |
} | |