yading@11: /* yading@11: * WAV muxer yading@11: * Copyright (c) 2001, 2002 Fabrice Bellard yading@11: * yading@11: * Sony Wave64 muxer yading@11: * Copyright (c) 2012 Paul B Mahol yading@11: * yading@11: * WAV muxer RF64 support yading@11: * Copyright (c) 2013 Daniel Verkamp yading@11: * yading@11: * This file is part of FFmpeg. yading@11: * yading@11: * FFmpeg is free software; you can redistribute it and/or yading@11: * modify it under the terms of the GNU Lesser General Public yading@11: * License as published by the Free Software Foundation; either yading@11: * version 2.1 of the License, or (at your option) any later version. yading@11: * yading@11: * FFmpeg is distributed in the hope that it will be useful, yading@11: * but WITHOUT ANY WARRANTY; without even the implied warranty of yading@11: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU yading@11: * Lesser General Public License for more details. yading@11: * yading@11: * You should have received a copy of the GNU Lesser General Public yading@11: * License along with FFmpeg; if not, write to the Free Software yading@11: * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA yading@11: */ yading@11: yading@11: #include yading@11: #include yading@11: yading@11: #include "libavutil/dict.h" yading@11: #include "libavutil/common.h" yading@11: #include "libavutil/mathematics.h" yading@11: #include "libavutil/opt.h" yading@11: yading@11: #include "avformat.h" yading@11: #include "avio.h" yading@11: #include "avio_internal.h" yading@11: #include "internal.h" yading@11: #include "riff.h" yading@11: yading@11: #define RF64_AUTO (-1) yading@11: #define RF64_NEVER 0 yading@11: #define RF64_ALWAYS 1 yading@11: yading@11: typedef struct WAVMuxContext { yading@11: const AVClass *class; yading@11: int64_t data; yading@11: int64_t fact_pos; yading@11: int64_t ds64; yading@11: int64_t minpts; yading@11: int64_t maxpts; yading@11: int last_duration; yading@11: int write_bext; yading@11: int rf64; yading@11: } WAVMuxContext; yading@11: yading@11: #if CONFIG_WAV_MUXER yading@11: static inline void bwf_write_bext_string(AVFormatContext *s, const char *key, int maxlen) yading@11: { yading@11: AVDictionaryEntry *tag; yading@11: int len = 0; yading@11: yading@11: if (tag = av_dict_get(s->metadata, key, NULL, 0)) { yading@11: len = strlen(tag->value); yading@11: len = FFMIN(len, maxlen); yading@11: avio_write(s->pb, tag->value, len); yading@11: } yading@11: yading@11: ffio_fill(s->pb, 0, maxlen - len); yading@11: } yading@11: yading@11: static void bwf_write_bext_chunk(AVFormatContext *s) yading@11: { yading@11: AVDictionaryEntry *tmp_tag; yading@11: uint64_t time_reference = 0; yading@11: int64_t bext = ff_start_tag(s->pb, "bext"); yading@11: yading@11: bwf_write_bext_string(s, "description", 256); yading@11: bwf_write_bext_string(s, "originator", 32); yading@11: bwf_write_bext_string(s, "originator_reference", 32); yading@11: bwf_write_bext_string(s, "origination_date", 10); yading@11: bwf_write_bext_string(s, "origination_time", 8); yading@11: yading@11: if (tmp_tag = av_dict_get(s->metadata, "time_reference", NULL, 0)) yading@11: time_reference = strtoll(tmp_tag->value, NULL, 10); yading@11: avio_wl64(s->pb, time_reference); yading@11: avio_wl16(s->pb, 1); // set version to 1 yading@11: yading@11: if (tmp_tag = av_dict_get(s->metadata, "umid", NULL, 0)) { yading@11: unsigned char umidpart_str[17] = {0}; yading@11: int i; yading@11: uint64_t umidpart; yading@11: int len = strlen(tmp_tag->value+2); yading@11: yading@11: for (i = 0; i < len/16; i++) { yading@11: memcpy(umidpart_str, tmp_tag->value + 2 + (i*16), 16); yading@11: umidpart = strtoll(umidpart_str, NULL, 16); yading@11: avio_wb64(s->pb, umidpart); yading@11: } yading@11: ffio_fill(s->pb, 0, 64 - i*8); yading@11: } else yading@11: ffio_fill(s->pb, 0, 64); // zero UMID yading@11: yading@11: ffio_fill(s->pb, 0, 190); // Reserved yading@11: yading@11: if (tmp_tag = av_dict_get(s->metadata, "coding_history", NULL, 0)) yading@11: avio_put_str(s->pb, tmp_tag->value); yading@11: yading@11: ff_end_tag(s->pb, bext); yading@11: } yading@11: yading@11: static int wav_write_header(AVFormatContext *s) yading@11: { yading@11: WAVMuxContext *wav = s->priv_data; yading@11: AVIOContext *pb = s->pb; yading@11: int64_t fmt; yading@11: yading@11: if (wav->rf64 == RF64_ALWAYS) { yading@11: ffio_wfourcc(pb, "RF64"); yading@11: avio_wl32(pb, -1); /* RF64 chunk size: use size in ds64 */ yading@11: } else { yading@11: ffio_wfourcc(pb, "RIFF"); yading@11: avio_wl32(pb, 0); /* file length */ yading@11: } yading@11: yading@11: ffio_wfourcc(pb, "WAVE"); yading@11: yading@11: if (wav->rf64 != RF64_NEVER) { yading@11: /* write empty ds64 chunk or JUNK chunk to reserve space for ds64 */ yading@11: ffio_wfourcc(pb, wav->rf64 == RF64_ALWAYS ? "ds64" : "JUNK"); yading@11: avio_wl32(pb, 28); /* chunk size */ yading@11: wav->ds64 = avio_tell(pb); yading@11: ffio_fill(pb, 0, 28); yading@11: } yading@11: yading@11: /* format header */ yading@11: fmt = ff_start_tag(pb, "fmt "); yading@11: if (ff_put_wav_header(pb, s->streams[0]->codec) < 0) { yading@11: av_log(s, AV_LOG_ERROR, "%s codec not supported in WAVE format\n", yading@11: s->streams[0]->codec->codec ? s->streams[0]->codec->codec->name : "NONE"); yading@11: return -1; yading@11: } yading@11: ff_end_tag(pb, fmt); yading@11: yading@11: if (s->streams[0]->codec->codec_tag != 0x01 /* hence for all other than PCM */ yading@11: && s->pb->seekable) { yading@11: wav->fact_pos = ff_start_tag(pb, "fact"); yading@11: avio_wl32(pb, 0); yading@11: ff_end_tag(pb, wav->fact_pos); yading@11: } yading@11: yading@11: if (wav->write_bext) yading@11: bwf_write_bext_chunk(s); yading@11: yading@11: avpriv_set_pts_info(s->streams[0], 64, 1, s->streams[0]->codec->sample_rate); yading@11: wav->maxpts = wav->last_duration = 0; yading@11: wav->minpts = INT64_MAX; yading@11: yading@11: /* info header */ yading@11: ff_riff_write_info(s); yading@11: yading@11: /* data header */ yading@11: wav->data = ff_start_tag(pb, "data"); yading@11: yading@11: avio_flush(pb); yading@11: yading@11: return 0; yading@11: } yading@11: yading@11: static int wav_write_packet(AVFormatContext *s, AVPacket *pkt) yading@11: { yading@11: AVIOContext *pb = s->pb; yading@11: WAVMuxContext *wav = s->priv_data; yading@11: avio_write(pb, pkt->data, pkt->size); yading@11: if(pkt->pts != AV_NOPTS_VALUE) { yading@11: wav->minpts = FFMIN(wav->minpts, pkt->pts); yading@11: wav->maxpts = FFMAX(wav->maxpts, pkt->pts); yading@11: wav->last_duration = pkt->duration; yading@11: } else yading@11: av_log(s, AV_LOG_ERROR, "wav_write_packet: NOPTS\n"); yading@11: return 0; yading@11: } yading@11: yading@11: static int wav_write_trailer(AVFormatContext *s) yading@11: { yading@11: AVIOContext *pb = s->pb; yading@11: WAVMuxContext *wav = s->priv_data; yading@11: int64_t file_size, data_size; yading@11: int64_t number_of_samples = 0; yading@11: int rf64 = 0; yading@11: yading@11: avio_flush(pb); yading@11: yading@11: if (s->pb->seekable) { yading@11: /* update file size */ yading@11: file_size = avio_tell(pb); yading@11: data_size = file_size - wav->data; yading@11: if (wav->rf64 == RF64_ALWAYS || (wav->rf64 == RF64_AUTO && file_size - 8 > UINT32_MAX)) { yading@11: rf64 = 1; yading@11: } else { yading@11: avio_seek(pb, 4, SEEK_SET); yading@11: avio_wl32(pb, (uint32_t)(file_size - 8)); yading@11: avio_seek(pb, file_size, SEEK_SET); yading@11: yading@11: ff_end_tag(pb, wav->data); yading@11: avio_flush(pb); yading@11: } yading@11: yading@11: number_of_samples = av_rescale(wav->maxpts - wav->minpts + wav->last_duration, yading@11: s->streams[0]->codec->sample_rate * (int64_t)s->streams[0]->time_base.num, yading@11: s->streams[0]->time_base.den); yading@11: yading@11: if(s->streams[0]->codec->codec_tag != 0x01) { yading@11: /* Update num_samps in fact chunk */ yading@11: avio_seek(pb, wav->fact_pos, SEEK_SET); yading@11: if (rf64 || (wav->rf64 == RF64_AUTO && number_of_samples > UINT32_MAX)) { yading@11: rf64 = 1; yading@11: avio_wl32(pb, -1); yading@11: } else { yading@11: avio_wl32(pb, number_of_samples); yading@11: avio_seek(pb, file_size, SEEK_SET); yading@11: avio_flush(pb); yading@11: } yading@11: } yading@11: yading@11: if (rf64) { yading@11: /* overwrite RIFF with RF64 */ yading@11: avio_seek(pb, 0, SEEK_SET); yading@11: ffio_wfourcc(pb, "RF64"); yading@11: avio_wl32(pb, -1); yading@11: yading@11: /* write ds64 chunk (overwrite JUNK if rf64 == RF64_AUTO) */ yading@11: avio_seek(pb, wav->ds64 - 8, SEEK_SET); yading@11: ffio_wfourcc(pb, "ds64"); yading@11: avio_wl32(pb, 28); /* ds64 chunk size */ yading@11: avio_wl64(pb, file_size - 8); /* RF64 chunk size */ yading@11: avio_wl64(pb, data_size); /* data chunk size */ yading@11: avio_wl64(pb, number_of_samples); /* fact chunk number of samples */ yading@11: avio_wl32(pb, 0); /* number of table entries for non-'data' chunks */ yading@11: yading@11: /* write -1 in data chunk size */ yading@11: avio_seek(pb, wav->data - 4, SEEK_SET); yading@11: avio_wl32(pb, -1); yading@11: yading@11: avio_seek(pb, file_size, SEEK_SET); yading@11: avio_flush(pb); yading@11: } yading@11: } yading@11: return 0; yading@11: } yading@11: yading@11: #define OFFSET(x) offsetof(WAVMuxContext, x) yading@11: #define ENC AV_OPT_FLAG_ENCODING_PARAM yading@11: static const AVOption options[] = { yading@11: { "write_bext", "Write BEXT chunk.", OFFSET(write_bext), AV_OPT_TYPE_INT, { .i64 = 0 }, 0, 1, ENC }, yading@11: { "rf64", "Use RF64 header rather than RIFF for large files.", OFFSET(rf64), AV_OPT_TYPE_INT, { .i64 = RF64_NEVER },-1, 1, ENC, "rf64" }, yading@11: { "auto", "Write RF64 header if file grows large enough.", 0, AV_OPT_TYPE_CONST, { .i64 = RF64_AUTO }, 0, 0, ENC, "rf64" }, yading@11: { "always", "Always write RF64 header regardless of file size.", 0, AV_OPT_TYPE_CONST, { .i64 = RF64_ALWAYS }, 0, 0, ENC, "rf64" }, yading@11: { "never", "Never write RF64 header regardless of file size.", 0, AV_OPT_TYPE_CONST, { .i64 = RF64_NEVER }, 0, 0, ENC, "rf64" }, yading@11: { NULL }, yading@11: }; yading@11: yading@11: static const AVClass wav_muxer_class = { yading@11: .class_name = "WAV muxer", yading@11: .item_name = av_default_item_name, yading@11: .option = options, yading@11: .version = LIBAVUTIL_VERSION_INT, yading@11: }; yading@11: yading@11: AVOutputFormat ff_wav_muxer = { yading@11: .name = "wav", yading@11: .long_name = NULL_IF_CONFIG_SMALL("WAV / WAVE (Waveform Audio)"), yading@11: .mime_type = "audio/x-wav", yading@11: .extensions = "wav", yading@11: .priv_data_size = sizeof(WAVMuxContext), yading@11: .audio_codec = AV_CODEC_ID_PCM_S16LE, yading@11: .video_codec = AV_CODEC_ID_NONE, yading@11: .write_header = wav_write_header, yading@11: .write_packet = wav_write_packet, yading@11: .write_trailer = wav_write_trailer, yading@11: .flags = AVFMT_TS_NONSTRICT, yading@11: .codec_tag = (const AVCodecTag* const []){ ff_codec_wav_tags, 0 }, yading@11: .priv_class = &wav_muxer_class, yading@11: }; yading@11: #endif /* CONFIG_WAV_MUXER */ yading@11: yading@11: #if CONFIG_W64_MUXER yading@11: #include "w64.h" yading@11: yading@11: static void start_guid(AVIOContext *pb, const uint8_t *guid, int64_t *pos) yading@11: { yading@11: *pos = avio_tell(pb); yading@11: yading@11: avio_write(pb, guid, 16); yading@11: avio_wl64(pb, INT64_MAX); yading@11: } yading@11: yading@11: static void end_guid(AVIOContext *pb, int64_t start) yading@11: { yading@11: int64_t end, pos = avio_tell(pb); yading@11: yading@11: end = FFALIGN(pos, 8); yading@11: ffio_fill(pb, 0, end - pos); yading@11: avio_seek(pb, start + 16, SEEK_SET); yading@11: avio_wl64(pb, end - start); yading@11: avio_seek(pb, end, SEEK_SET); yading@11: } yading@11: yading@11: static int w64_write_header(AVFormatContext *s) yading@11: { yading@11: WAVMuxContext *wav = s->priv_data; yading@11: AVIOContext *pb = s->pb; yading@11: int64_t start; yading@11: int ret; yading@11: yading@11: avio_write(pb, ff_w64_guid_riff, sizeof(ff_w64_guid_riff)); yading@11: avio_wl64(pb, -1); yading@11: avio_write(pb, ff_w64_guid_wave, sizeof(ff_w64_guid_wave)); yading@11: start_guid(pb, ff_w64_guid_fmt, &start); yading@11: if ((ret = ff_put_wav_header(pb, s->streams[0]->codec)) < 0) { yading@11: av_log(s, AV_LOG_ERROR, "%s codec not supported\n", yading@11: s->streams[0]->codec->codec ? s->streams[0]->codec->codec->name : "NONE"); yading@11: return ret; yading@11: } yading@11: end_guid(pb, start); yading@11: yading@11: if (s->streams[0]->codec->codec_tag != 0x01 /* hence for all other than PCM */ yading@11: && s->pb->seekable) { yading@11: start_guid(pb, ff_w64_guid_fact, &wav->fact_pos); yading@11: avio_wl64(pb, 0); yading@11: end_guid(pb, wav->fact_pos); yading@11: } yading@11: yading@11: start_guid(pb, ff_w64_guid_data, &wav->data); yading@11: yading@11: return 0; yading@11: } yading@11: yading@11: static int w64_write_trailer(AVFormatContext *s) yading@11: { yading@11: AVIOContext *pb = s->pb; yading@11: WAVMuxContext *wav = s->priv_data; yading@11: int64_t file_size; yading@11: yading@11: if (pb->seekable) { yading@11: end_guid(pb, wav->data); yading@11: yading@11: file_size = avio_tell(pb); yading@11: avio_seek(pb, 16, SEEK_SET); yading@11: avio_wl64(pb, file_size); yading@11: yading@11: if (s->streams[0]->codec->codec_tag != 0x01) { yading@11: int64_t number_of_samples; yading@11: yading@11: number_of_samples = av_rescale(wav->maxpts - wav->minpts + wav->last_duration, yading@11: s->streams[0]->codec->sample_rate * (int64_t)s->streams[0]->time_base.num, yading@11: s->streams[0]->time_base.den); yading@11: avio_seek(pb, wav->fact_pos + 24, SEEK_SET); yading@11: avio_wl64(pb, number_of_samples); yading@11: } yading@11: yading@11: avio_seek(pb, file_size, SEEK_SET); yading@11: avio_flush(pb); yading@11: } yading@11: yading@11: return 0; yading@11: } yading@11: yading@11: AVOutputFormat ff_w64_muxer = { yading@11: .name = "w64", yading@11: .long_name = NULL_IF_CONFIG_SMALL("Sony Wave64"), yading@11: .extensions = "w64", yading@11: .priv_data_size = sizeof(WAVMuxContext), yading@11: .audio_codec = AV_CODEC_ID_PCM_S16LE, yading@11: .video_codec = AV_CODEC_ID_NONE, yading@11: .write_header = w64_write_header, yading@11: .write_packet = wav_write_packet, yading@11: .write_trailer = w64_write_trailer, yading@11: .flags = AVFMT_TS_NONSTRICT, yading@11: .codec_tag = (const AVCodecTag* const []){ ff_codec_wav_tags, 0 }, yading@11: }; yading@11: #endif /* CONFIG_W64_MUXER */