root/applications/dashcast/audio_muxer.c

/* [<][>][^][v][top][bottom][index][help] */

DEFINITIONS

This source file includes following definitions.
  1. dc_gpac_audio_moov_create
  2. dc_gpac_audio_isom_open_seg
  3. dc_gpac_audio_isom_write
  4. dc_gpac_audio_isom_close_seg
  5. dc_gpac_audio_isom_close
  6. dc_ffmpeg_audio_muxer_open
  7. dc_ffmpeg_audio_muxer_write
  8. dc_ffmpeg_audio_muxer_close
  9. dc_audio_muxer_init
  10. dc_audio_muxer_free
  11. dc_audio_muxer_open
  12. dc_audio_muxer_write
  13. dc_audio_muxer_close

/*
 *                      GPAC - Multimedia Framework C SDK
 *
 *                      Authors: Arash Shafiei
 *                      Copyright (c) Telecom ParisTech 2000-2013
 *                                      All rights reserved
 *
 *  This file is part of GPAC / dashcast
 *
 *  GPAC 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, or (at your option)
 *  any later version.
 *
 *  GPAC 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 this library; see the file COPYING.  If not, write to
 *  the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
 *
 */

#include "audio_muxer.h"
#include "libavformat/avio.h"

#ifndef GPAC_DISABLE_ISOM

int dc_gpac_audio_moov_create(AudioOutputFile *audio_output_file, char *filename)
{
        GF_Err ret;
        u32 di, track;
        u8 bpsample;
        GF_ESD *esd;
#ifndef GPAC_DISABLE_AV_PARSERS
        GF_M4ADecSpecInfo acfg;
#endif
        AVCodecContext *audio_codec_ctx = audio_output_file->codec_ctx;

        audio_output_file->isof = gf_isom_open(filename, GF_ISOM_OPEN_WRITE, NULL);
        if (!audio_output_file->isof) {
                GF_LOG(GF_LOG_ERROR, GF_LOG_DASH, ("Cannot open iso file %s\n", filename));
                return -1;
        }

        esd = gf_odf_desc_esd_new(2);
        if (!esd) {
                GF_LOG(GF_LOG_ERROR, GF_LOG_DASH, ("Cannot create GF_ESD\n"));
                return -1;
        }

        esd->decoderConfig = (GF_DecoderConfig *) gf_odf_desc_new(GF_ODF_DCD_TAG);
        esd->slConfig = (GF_SLConfig *) gf_odf_desc_new(GF_ODF_SLC_TAG);
        esd->decoderConfig->streamType = GF_STREAM_AUDIO;
        if (!strcmp(audio_output_file->codec_ctx->codec->name, "aac")) { //TODO: find an automatic table
                esd->decoderConfig->objectTypeIndication = GPAC_OTI_AUDIO_AAC_MPEG4;
                esd->decoderConfig->bufferSizeDB = 20;
                esd->slConfig->timestampResolution = audio_codec_ctx->sample_rate;
                esd->decoderConfig->decoderSpecificInfo = (GF_DefaultDescriptor *) gf_odf_desc_new(GF_ODF_DSI_TAG);
                esd->ESID = 1;

#ifndef GPAC_DISABLE_AV_PARSERS
                memset(&acfg, 0, sizeof(GF_M4ADecSpecInfo));
                acfg.base_object_type = GF_M4A_AAC_LC;
                acfg.base_sr = audio_codec_ctx->sample_rate;
                acfg.nb_chan = audio_codec_ctx->channels;
                acfg.sbr_object_type = 0;
                acfg.audioPL = gf_m4a_get_profile(&acfg);

                ret = gf_m4a_write_config(&acfg, &esd->decoderConfig->decoderSpecificInfo->data, &esd->decoderConfig->decoderSpecificInfo->dataLength);
                assert(ret == GF_OK);
#endif
        } else {
                if (strcmp(audio_output_file->codec_ctx->codec->name, "mp2")) {
                        GF_LOG(GF_LOG_WARNING, GF_LOG_DASH, ("Unlisted codec, setting GPAC_OTI_AUDIO_MPEG1 descriptor.\n"));
                }
                esd->decoderConfig->objectTypeIndication = GPAC_OTI_AUDIO_MPEG1;
                esd->decoderConfig->bufferSizeDB = 20;
                esd->slConfig->timestampResolution = audio_codec_ctx->sample_rate;
                esd->decoderConfig->decoderSpecificInfo = (GF_DefaultDescriptor *) gf_odf_desc_new(GF_ODF_DSI_TAG);
                esd->ESID = 1;

#ifndef GPAC_DISABLE_AV_PARSERS
                memset(&acfg, 0, sizeof(GF_M4ADecSpecInfo));
                acfg.base_object_type = GF_M4A_LAYER2;
                acfg.base_sr = audio_codec_ctx->sample_rate;
                acfg.nb_chan = audio_codec_ctx->channels;
                acfg.sbr_object_type = 0;
                acfg.audioPL = gf_m4a_get_profile(&acfg);

                ret = gf_m4a_write_config(&acfg, &esd->decoderConfig->decoderSpecificInfo->data, &esd->decoderConfig->decoderSpecificInfo->dataLength);
                assert(ret == GF_OK);
#endif
        }

        //gf_isom_store_movie_config(video_output_file->isof, 0);
        track = gf_isom_new_track(audio_output_file->isof, esd->ESID, GF_ISOM_MEDIA_AUDIO, audio_codec_ctx->sample_rate);
        GF_LOG(GF_LOG_DEBUG, GF_LOG_DASH, ("TimeScale: %d \n", audio_codec_ctx->time_base.den));
        if (!track) {
                GF_LOG(GF_LOG_ERROR, GF_LOG_DASH, ("Cannot create new track\n"));
                return -1;
        }

        ret = gf_isom_set_track_enabled(audio_output_file->isof, track, 1);
        if (ret != GF_OK) {
                GF_LOG(GF_LOG_ERROR, GF_LOG_DASH, ("%s: gf_isom_set_track_enabled\n", gf_error_to_string(ret)));
                return -1;
        }

//      if (!esd->ESID) esd->ESID = gf_isom_get_track_id(audio_output_file->isof, track);

        ret = gf_isom_new_mpeg4_description(audio_output_file->isof, track, esd, NULL, NULL, &di);
        if (ret != GF_OK) {
                GF_LOG(GF_LOG_ERROR, GF_LOG_DASH, ("%s: gf_isom_new_mpeg4_description\n", gf_error_to_string(ret)));
                return -1;
        }

        gf_odf_desc_del((GF_Descriptor *) esd);
        esd = NULL;

        bpsample = av_get_bytes_per_sample(audio_output_file->codec_ctx->sample_fmt) * 8;

        ret = gf_isom_set_audio_info(audio_output_file->isof, track, di, audio_codec_ctx->sample_rate, audio_output_file->codec_ctx->channels, bpsample);
        if (ret != GF_OK) {
                GF_LOG(GF_LOG_ERROR, GF_LOG_DASH, ("%s: gf_isom_set_audio_info\n", gf_error_to_string(ret)));
                return -1;
        }

#ifndef GPAC_DISABLE_AV_PARSERS
        ret = gf_isom_set_pl_indication(audio_output_file->isof, GF_ISOM_PL_AUDIO, acfg.audioPL);
        if (ret != GF_OK) {
                GF_LOG(GF_LOG_ERROR, GF_LOG_DASH, ("%s: gf_isom_set_pl_indication\n", gf_error_to_string(ret)));
                return -1;
        }
#endif

        GF_LOG(GF_LOG_DEBUG, GF_LOG_DASH, ("time scale: %d  sample dur: %d \n", audio_codec_ctx->time_base.den, audio_output_file->codec_ctx->frame_size));

        ret = gf_isom_setup_track_fragment(audio_output_file->isof, track, 1, audio_output_file->codec_ctx->frame_size, 0, 0, 0, 0);
        if (ret != GF_OK) {
                GF_LOG(GF_LOG_ERROR, GF_LOG_DASH, ("%s: gf_isom_setup_track_fragment\n", gf_error_to_string(ret)));
                return -1;
        }

        //gf_isom_add_track_to_root_od(video_output_file->isof,1);

        ret = gf_isom_finalize_for_fragment(audio_output_file->isof, 1);
        if (ret != GF_OK) {
                GF_LOG(GF_LOG_ERROR, GF_LOG_DASH, ("%s: gf_isom_finalize_for_fragment\n", gf_error_to_string(ret)));
                return -1;
        }

        ret = gf_media_get_rfc_6381_codec_name(audio_output_file->isof, track, audio_output_file->audio_data_conf->codec6381, GF_FALSE, GF_FALSE);
        if (ret != GF_OK) return -1;
        return 0;
}

int dc_gpac_audio_isom_open_seg(AudioOutputFile *audio_output_file, char *filename)
{
        GF_Err ret;
        ret = gf_isom_start_segment(audio_output_file->isof, filename, GF_TRUE);
        if (ret != GF_OK) {
                GF_LOG(GF_LOG_ERROR, GF_LOG_DASH, ("%s: gf_isom_start_segment\n", gf_error_to_string(ret)));
                return -1;
        }

        GF_LOG(GF_LOG_DEBUG, GF_LOG_DASH, ("[DashCast] Audio segment %s started at "LLU"\n", filename, gf_net_get_utc() ));

        audio_output_file->dts = 0;

        return 0;
}

int dc_gpac_audio_isom_write(AudioOutputFile *audio_output_file)
{
        GF_Err ret;
        audio_output_file->sample->data = (char *) audio_output_file->packet.data;
        audio_output_file->sample->dataLength = audio_output_file->packet.size;

        audio_output_file->sample->DTS = audio_output_file->dts; //audio_output_file->aframe->pts;
        audio_output_file->sample->IsRAP = RAP; //audio_output_file->aframe->key_frame;//audio_codec_ctx->coded_frame->key_frame;

        ret = gf_isom_fragment_add_sample(audio_output_file->isof, 1, audio_output_file->sample, 1, audio_output_file->codec_ctx->frame_size, 0, 0, 0);
        audio_output_file->dts += audio_output_file->codec_ctx->frame_size;
        if (ret != GF_OK) {
                GF_LOG(GF_LOG_ERROR, GF_LOG_DASH, ("%s: gf_isom_fragment_add_sample\n", gf_error_to_string(ret)));
                return -1;
        }
        return 0;
}

int dc_gpac_audio_isom_close_seg(AudioOutputFile *audio_output_file)
{
        GF_Err ret;
        ret = gf_isom_close_segment(audio_output_file->isof, 0, 0,0, 0, 0, 0, 1, audio_output_file->seg_marker, NULL, NULL);
        if (ret != GF_OK) {
                GF_LOG(GF_LOG_ERROR, GF_LOG_DASH, ("%s: gf_isom_close_segment\n", gf_error_to_string(ret)));
                return -1;
        }
        GF_LOG(GF_LOG_DEBUG, GF_LOG_DASH, ("[DashCast] Audio segment closed at "LLU"\n", gf_net_get_utc() ));

        //audio_output_file->acc_samples = 0;

        return 0;
}

int dc_gpac_audio_isom_close(AudioOutputFile *audio_output_file)
{
        GF_Err ret;
        ret = gf_isom_close(audio_output_file->isof);
        if (ret != GF_OK) {
                GF_LOG(GF_LOG_ERROR, GF_LOG_DASH, ("%s: gf_isom_close\n", gf_error_to_string(ret)));
                return -1;
        }

        //audio_output_file->acc_samples = 0;

        return 0;
}

#endif



int dc_ffmpeg_audio_muxer_open(AudioOutputFile *audio_output_file, char *filename)
{
        AVStream *audio_stream;
        AVOutputFormat *output_fmt;
        AVDictionary *opts = NULL;

        AVCodecContext *audio_codec_ctx = audio_output_file->codec_ctx;
        audio_output_file->av_fmt_ctx = NULL;

//      strcpy(audio_output_file->filename, audio_data_conf->filename);
//      audio_output_file->abr = audio_data_conf->bitrate;
//      audio_output_file->asr = audio_data_conf->samplerate;
//      audio_output_file->ach = audio_data_conf->channels;
//      strcpy(audio_output_file->codec, audio_data_conf->codec);

        /* Find output format */
        output_fmt = av_guess_format(NULL, filename, NULL);
        if (!output_fmt) {
                GF_LOG(GF_LOG_ERROR, GF_LOG_DASH, ("Cannot find suitable output format\n"));
                return -1;
        }

        audio_output_file->av_fmt_ctx = avformat_alloc_context();
        if (!audio_output_file->av_fmt_ctx) {
                GF_LOG(GF_LOG_ERROR, GF_LOG_DASH, ("Cannot allocate memory for pOutVideoFormatCtx\n"));
                return -1;
        }

        audio_output_file->av_fmt_ctx->oformat = output_fmt;
        strcpy(audio_output_file->av_fmt_ctx->filename, filename);

        /* Open the output file */
        if (!(output_fmt->flags & AVFMT_NOFILE)) {
                if (avio_open(&audio_output_file->av_fmt_ctx->pb, filename, URL_WRONLY) < 0) {
                        GF_LOG(GF_LOG_ERROR, GF_LOG_DASH, ("Cannot not open '%s'\n", filename));
                        return -1;
                }
        }

        audio_stream = avformat_new_stream(audio_output_file->av_fmt_ctx, audio_output_file->codec);
        if (!audio_stream) {
                GF_LOG(GF_LOG_ERROR, GF_LOG_DASH, ("Cannot create output video stream\n"));
                return -1;
        }

        audio_stream->codec->codec_id = audio_output_file->codec->id;
        audio_stream->codec->codec_type = AVMEDIA_TYPE_AUDIO;
        audio_stream->codec->bit_rate = audio_codec_ctx->bit_rate;//audio_output_file->audio_data_conf->bitrate;
        audio_stream->codec->sample_rate = audio_codec_ctx->sample_rate;//audio_output_file->audio_data_conf->samplerate;
        audio_stream->codec->channels = audio_codec_ctx->channels;//audio_output_file->audio_data_conf->channels;
        assert(audio_codec_ctx->codec->sample_fmts);
        audio_stream->codec->sample_fmt = audio_codec_ctx->codec->sample_fmts[0];

//      if (audio_output_file->av_fmt_ctx->oformat->flags & AVFMT_GLOBALHEADER)
//              audio_output_file->codec_ctx->flags |= CODEC_FLAG_GLOBAL_HEADER;

        //video_stream->codec = video_output_file->codec_ctx;

        /* open the video codec */
        av_dict_set(&opts, "strict", "experimental", 0);
        if (avcodec_open2(audio_stream->codec, audio_output_file->codec, &opts) < 0) {
                GF_LOG(GF_LOG_ERROR, GF_LOG_DASH, ("Cannot open output video codec\n"));
                av_dict_free(&opts);
                return -1;
        }
        av_dict_free(&opts);

        avformat_write_header(audio_output_file->av_fmt_ctx, NULL);

        return 0;
}

int dc_ffmpeg_audio_muxer_write(AudioOutputFile *audio_output_file)
{
        AVStream *audio_stream = audio_output_file->av_fmt_ctx->streams[audio_output_file->astream_idx];
        AVCodecContext *audio_codec_ctx = audio_stream->codec;

        audio_output_file->packet.stream_index = audio_stream->index;

        if (audio_output_file->packet.pts != AV_NOPTS_VALUE)
                audio_output_file->packet.pts = av_rescale_q(audio_output_file->packet.pts, audio_codec_ctx->time_base, audio_stream->time_base);

        if (audio_output_file->packet.duration > 0)
                audio_output_file->packet.duration = (int)av_rescale_q(audio_output_file->packet.duration, audio_codec_ctx->time_base, audio_stream->time_base);
        /*
         * if (pkt.pts != AV_NOPTS_VALUE)
         * pkt.pts = av_rescale_q(pkt.pts, audioEncCtx->time_base, audioStream->time_base);
         */

        audio_output_file->packet.flags |= AV_PKT_FLAG_KEY;

        if (av_interleaved_write_frame(audio_output_file->av_fmt_ctx, &audio_output_file->packet) != 0) {
                GF_LOG(GF_LOG_ERROR, GF_LOG_DASH, ("Writing frame is not successful\n"));
                av_free_packet(&audio_output_file->packet);
                return -1;
        }

        av_free_packet(&audio_output_file->packet);

        return 0;
}

int dc_ffmpeg_audio_muxer_close(AudioOutputFile *audio_output_file)
{
        u32 i;

        av_write_trailer(audio_output_file->av_fmt_ctx);
        avio_close(audio_output_file->av_fmt_ctx->pb);

        // free the streams
        for (i = 0; i < audio_output_file->av_fmt_ctx->nb_streams; i++) {
                avcodec_close(audio_output_file->av_fmt_ctx->streams[i]->codec);
                av_freep(&audio_output_file->av_fmt_ctx->streams[i]->info);
        }

        //video_output_file->av_fmt_ctx->streams[video_output_file->vstream_idx]->codec = NULL;
        avformat_free_context(audio_output_file->av_fmt_ctx);

        //audio_output_file->acc_samples = 0;

        return 0;

}

int dc_audio_muxer_init(AudioOutputFile *audio_output_file, AudioDataConf *audio_data_conf, AudioMuxerType muxer_type, int frame_per_seg, int frame_per_frag, u32 seg_marker)
{
        char name[GF_MAX_PATH];
        snprintf(name, sizeof(name), "audio encoder %s", audio_data_conf->filename);
        dc_consumer_init(&audio_output_file->consumer, AUDIO_CB_SIZE, name);

#ifndef GPAC_DISABLE_ISOM
        audio_output_file->sample = gf_isom_sample_new();
        audio_output_file->isof = NULL;
#endif

        audio_output_file->muxer_type = muxer_type;
        audio_output_file->frame_per_seg = frame_per_seg;
        audio_output_file->frame_per_frag = frame_per_frag;
        audio_output_file->seg_marker = seg_marker;
        return 0;
}

void dc_audio_muxer_free(AudioOutputFile *audio_output_file)
{
#ifndef GPAC_DISABLE_ISOM
        if (audio_output_file->isof != NULL) {
                gf_isom_close(audio_output_file->isof);
        }
        //gf_isom_sample_del(&audio_output_file->sample);
#endif
}

GF_Err dc_audio_muxer_open(AudioOutputFile *audio_output_file, char *directory, char *id_name, int seg)
{
        GF_Err ret = GF_NOT_SUPPORTED;
        char name[GF_MAX_PATH];

        switch (audio_output_file->muxer_type) {
        case FFMPEG_AUDIO_MUXER:
                snprintf(name, sizeof(name), "%s/%s_%d_ffmpeg.mp4", directory, id_name, seg);
                return dc_ffmpeg_audio_muxer_open(audio_output_file, name);
#ifndef GPAC_DISABLE_ISOM
        case GPAC_AUDIO_MUXER:
                snprintf(name, sizeof(name), "%s/%s_%d_gpac.mp4", directory, id_name, seg);
                dc_gpac_audio_moov_create(audio_output_file, name);
                return dc_gpac_audio_isom_open_seg(audio_output_file, NULL);
        case GPAC_INIT_AUDIO_MUXER:
                if (seg == 1) {
                        snprintf(name, sizeof(name), "%s/%s_init_gpac.mp4", directory, id_name);
                        dc_gpac_audio_moov_create(audio_output_file, name);
                        audio_output_file->first_dts = 0;
                }
                snprintf(name, sizeof(name), "%s/%s_%d_gpac.m4s", directory, id_name, seg);
                ret = dc_gpac_audio_isom_open_seg(audio_output_file, name);
                return ret;
#endif
        default:
                ret = GF_BAD_PARAM;
                break;
        }

        return ret;
}

int dc_audio_muxer_write(AudioOutputFile *audio_output_file, int frame_nb, Bool insert_ntp)
{
        switch (audio_output_file->muxer_type) {
        case FFMPEG_AUDIO_MUXER:
                return dc_ffmpeg_audio_muxer_write(audio_output_file);
#ifndef GPAC_DISABLE_ISOM
        case GPAC_AUDIO_MUXER:
        case GPAC_INIT_AUDIO_MUXER:
                if (frame_nb % audio_output_file->frame_per_frag == 0) {
                        gf_isom_start_fragment(audio_output_file->isof, 1);
                        
                        if (insert_ntp) {
                                gf_isom_set_fragment_reference_time(audio_output_file->isof, 1, audio_output_file->frame_ntp, audio_output_file->first_dts * audio_output_file->codec_ctx->frame_size);
                        }

                        gf_isom_set_traf_base_media_decode_time(audio_output_file->isof, 1, audio_output_file->first_dts * audio_output_file->codec_ctx->frame_size);
                        audio_output_file->first_dts += audio_output_file->frame_per_frag;
                        GF_LOG(GF_LOG_DEBUG, GF_LOG_DASH, ("[DashCast] Audio start fragment first DTS %u at "LLU"\n", audio_output_file->first_dts, gf_net_get_utc() ));
                }
                dc_gpac_audio_isom_write(audio_output_file);
                if (frame_nb % audio_output_file->frame_per_frag == audio_output_file->frame_per_frag - 1) {
                        gf_isom_flush_fragments(audio_output_file->isof, 1);
                        GF_LOG(GF_LOG_DEBUG, GF_LOG_DASH, ("[DashCast] Audio flush fragment first DTS %u at "LLU"\n", audio_output_file->first_dts, gf_net_get_utc() ));
                }
                //TODO - do same as video, flush based on time in case of losses
                if (frame_nb + 1 == audio_output_file->frame_per_seg) {
                        return 1;
                }

                return 0;
#endif

        default:
                return GF_BAD_PARAM;
        }
        return GF_BAD_PARAM;
}

int dc_audio_muxer_close(AudioOutputFile *audio_output_file)
{
        switch (audio_output_file->muxer_type) {
        case FFMPEG_AUDIO_MUXER:
                return dc_ffmpeg_audio_muxer_close(audio_output_file);
#ifndef GPAC_DISABLE_ISOM
        case GPAC_AUDIO_MUXER:
                dc_gpac_audio_isom_close_seg(audio_output_file);
                return dc_gpac_audio_isom_close(audio_output_file);
        case GPAC_INIT_AUDIO_MUXER:
                return dc_gpac_audio_isom_close_seg(audio_output_file);
#endif
        default:
                return GF_BAD_PARAM;
        }

        return GF_BAD_PARAM;
}

/* [<][>][^][v][top][bottom][index][help] */