1/* 2 * AST muxer 3 * Copyright (c) 2012 James Almer 4 * 5 * This file is part of FFmpeg. 6 * 7 * FFmpeg is free software; you can redistribute it and/or 8 * modify it under the terms of the GNU Lesser General Public 9 * License as published by the Free Software Foundation; either 10 * version 2.1 of the License, or (at your option) any later version. 11 * 12 * FFmpeg is distributed in the hope that it will be useful, 13 * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 15 * Lesser General Public License for more details. 16 * 17 * You should have received a copy of the GNU Lesser General Public 18 * License along with FFmpeg; if not, write to the Free Software 19 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 20 */ 21 22#include "avformat.h" 23#include "avio_internal.h" 24#include "internal.h" 25#include "ast.h" 26#include "libavutil/mathematics.h" 27#include "libavutil/opt.h" 28 29typedef struct ASTMuxContext { 30 AVClass *class; 31 int64_t size; 32 int64_t samples; 33 int64_t loopstart; 34 int64_t loopend; 35 int fbs; 36} ASTMuxContext; 37 38#define CHECK_LOOP(type) \ 39 if (ast->loop ## type > 0) { \ 40 ast->loop ## type = av_rescale_rnd(ast->loop ## type, enc->sample_rate, 1000, AV_ROUND_DOWN); \ 41 if (ast->loop ## type < 0 || ast->loop ## type > UINT_MAX) { \ 42 av_log(s, AV_LOG_ERROR, "Invalid loop" #type " value\n"); \ 43 return AVERROR(EINVAL); \ 44 } \ 45 } 46 47static int ast_write_header(AVFormatContext *s) 48{ 49 ASTMuxContext *ast = s->priv_data; 50 AVIOContext *pb = s->pb; 51 AVCodecContext *enc; 52 unsigned int codec_tag; 53 54 if (s->nb_streams == 1) { 55 enc = s->streams[0]->codec; 56 } else { 57 av_log(s, AV_LOG_ERROR, "only one stream is supported\n"); 58 return AVERROR(EINVAL); 59 } 60 61 if (enc->codec_id == AV_CODEC_ID_ADPCM_AFC) { 62 av_log(s, AV_LOG_ERROR, "muxing ADPCM AFC is not implemented\n"); 63 return AVERROR_PATCHWELCOME; 64 } 65 66 codec_tag = ff_codec_get_tag(ff_codec_ast_tags, enc->codec_id); 67 if (!codec_tag) { 68 av_log(s, AV_LOG_ERROR, "unsupported codec\n"); 69 return AVERROR(EINVAL); 70 } 71 72 if (ast->loopend > 0 && ast->loopstart >= ast->loopend) { 73 av_log(s, AV_LOG_ERROR, "loopend can't be less or equal to loopstart\n"); 74 return AVERROR(EINVAL); 75 } 76 77 /* Convert milliseconds to samples */ 78 CHECK_LOOP(start) 79 CHECK_LOOP(end) 80 81 ffio_wfourcc(pb, "STRM"); 82 83 ast->size = avio_tell(pb); 84 avio_wb32(pb, 0); /* File size minus header */ 85 avio_wb16(pb, codec_tag); 86 avio_wb16(pb, 16); /* Bit depth */ 87 avio_wb16(pb, enc->channels); 88 avio_wb16(pb, 0); /* Loop flag */ 89 avio_wb32(pb, enc->sample_rate); 90 91 ast->samples = avio_tell(pb); 92 avio_wb32(pb, 0); /* Number of samples */ 93 avio_wb32(pb, 0); /* Loopstart */ 94 avio_wb32(pb, 0); /* Loopend */ 95 avio_wb32(pb, 0); /* Size of first block */ 96 97 /* Unknown */ 98 avio_wb32(pb, 0); 99 avio_wl32(pb, 0x7F); 100 avio_wb64(pb, 0); 101 avio_wb64(pb, 0); 102 avio_wb32(pb, 0); 103 104 avio_flush(pb); 105 106 return 0; 107} 108 109static int ast_write_packet(AVFormatContext *s, AVPacket *pkt) 110{ 111 AVIOContext *pb = s->pb; 112 ASTMuxContext *ast = s->priv_data; 113 AVCodecContext *enc = s->streams[0]->codec; 114 int size = pkt->size / enc->channels; 115 116 if (s->streams[0]->nb_frames == 0) 117 ast->fbs = size; 118 119 ffio_wfourcc(pb, "BLCK"); 120 avio_wb32(pb, size); /* Block size */ 121 122 /* padding */ 123 avio_wb64(pb, 0); 124 avio_wb64(pb, 0); 125 avio_wb64(pb, 0); 126 127 avio_write(pb, pkt->data, pkt->size); 128 129 return 0; 130} 131 132static int ast_write_trailer(AVFormatContext *s) 133{ 134 AVIOContext *pb = s->pb; 135 ASTMuxContext *ast = s->priv_data; 136 AVCodecContext *enc = s->streams[0]->codec; 137 int64_t file_size = avio_tell(pb); 138 int64_t samples = (file_size - 64 - (32 * s->streams[0]->nb_frames)) / enc->block_align; /* PCM_S16BE_PLANAR */ 139 140 av_log(s, AV_LOG_DEBUG, "total samples: %"PRId64"\n", samples); 141 142 if (s->pb->seekable) { 143 /* Number of samples */ 144 avio_seek(pb, ast->samples, SEEK_SET); 145 avio_wb32(pb, samples); 146 147 /* Loopstart if provided */ 148 if (ast->loopstart > 0) { 149 if (ast->loopstart >= samples) { 150 av_log(s, AV_LOG_WARNING, "Loopstart value is out of range and will be ignored\n"); 151 ast->loopstart = -1; 152 avio_skip(pb, 4); 153 } else 154 avio_wb32(pb, ast->loopstart); 155 } else 156 avio_skip(pb, 4); 157 158 /* Loopend if provided. Otherwise number of samples again */ 159 if (ast->loopend && ast->loopstart >= 0) { 160 if (ast->loopend > samples) { 161 av_log(s, AV_LOG_WARNING, "Loopend value is out of range and will be ignored\n"); 162 ast->loopend = samples; 163 } 164 avio_wb32(pb, ast->loopend); 165 } else { 166 avio_wb32(pb, samples); 167 } 168 169 /* Size of first block */ 170 avio_wb32(pb, ast->fbs); 171 172 /* File size minus header */ 173 avio_seek(pb, ast->size, SEEK_SET); 174 avio_wb32(pb, file_size - 64); 175 176 /* Loop flag */ 177 if (ast->loopstart >= 0) { 178 avio_skip(pb, 6); 179 avio_wb16(pb, 0xFFFF); 180 } 181 182 avio_seek(pb, file_size, SEEK_SET); 183 avio_flush(pb); 184 } 185 return 0; 186} 187 188#define OFFSET(obj) offsetof(ASTMuxContext, obj) 189static const AVOption options[] = { 190 { "loopstart", "Loopstart position in milliseconds.", OFFSET(loopstart), AV_OPT_TYPE_INT64, { .i64 = -1 }, -1, INT_MAX, AV_OPT_FLAG_ENCODING_PARAM }, 191 { "loopend", "Loopend position in milliseconds.", OFFSET(loopend), AV_OPT_TYPE_INT64, { .i64 = 0 }, 0, INT_MAX, AV_OPT_FLAG_ENCODING_PARAM }, 192 { NULL }, 193}; 194 195static const AVClass ast_muxer_class = { 196 .class_name = "AST muxer", 197 .item_name = av_default_item_name, 198 .option = options, 199 .version = LIBAVUTIL_VERSION_INT, 200}; 201 202AVOutputFormat ff_ast_muxer = { 203 .name = "ast", 204 .long_name = NULL_IF_CONFIG_SMALL("AST (Audio Stream)"), 205 .extensions = "ast", 206 .priv_data_size = sizeof(ASTMuxContext), 207 .audio_codec = AV_CODEC_ID_PCM_S16BE_PLANAR, 208 .video_codec = AV_CODEC_ID_NONE, 209 .write_header = ast_write_header, 210 .write_packet = ast_write_packet, 211 .write_trailer = ast_write_trailer, 212 .priv_class = &ast_muxer_class, 213 .codec_tag = (const AVCodecTag* const []){ff_codec_ast_tags, 0}, 214}; 215