ref: 6c5a354369d45ef8d0da13bdc9d459a05bdd2472
dir: /src/ffmpeg.c/
/*
* libSoX ffmpeg formats.
*
* Copyright 2007 Reuben Thomas <rrt@sc3d.org>
*
* Based on ffplay.c and output_example.c Copyright 2003 Fabrice Bellard
* Note: ffplay.c is distributed under the LGPL 2.1 or later;
* output_example.c is under the MIT license:
*-----------------------------------------------------------------------
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*-----------------------------------------------------------------------
*
* This library 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.
*
* This library 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. If not, write to the Free Software
* Foundation, Fifth Floor, 51 Franklin Street, Boston, MA 02111-1301,
* USA. */
#include "sox_i.h"
#include <assert.h>
#include <stdio.h>
#include <string.h>
#include <ctype.h>
#include <ffmpeg/avformat.h>
/* Private data for ffmpeg files */
typedef struct ffmpeg
{
int audio_index;
int audio_stream;
AVStream *audio_st;
uint8_t *audio_buf;
int audio_buf_index, audio_buf_size;
int16_t *samples;
int samples_index;
AVOutputFormat *fmt;
AVFormatContext *ctxt;
int audio_input_frame_size;
AVPacket audio_pkt;
uint8_t *audio_pkt_data;
int audio_pkt_size;
} *ffmpeg_t;
assert_static(sizeof(struct ffmpeg) <= SOX_MAX_FILE_PRIVSIZE,
/* else */ ffmpeg_PRIVSIZE_too_big);
/* open a given stream. Return 0 if OK */
static int stream_component_open(ffmpeg_t ffmpeg, int stream_index)
{
AVFormatContext *ic = ffmpeg->ctxt;
AVCodecContext *enc;
AVCodec *codec;
if (stream_index < 0 || stream_index >= (int)(ic->nb_streams))
return -1;
enc = ic->streams[stream_index]->codec;
/* hack for AC3. XXX: suppress that */
if (enc->channels > 2)
enc->channels = 2;
codec = avcodec_find_decoder(enc->codec_id);
enc->workaround_bugs = 1;
enc->error_resilience = 1;
if (!codec || avcodec_open(enc, codec) < 0)
return -1;
if (enc->codec_type != CODEC_TYPE_AUDIO) {
sox_fail("ffmpeg CODEC %x is not an audio CODEC", enc->codec_type);
return -1;
}
ffmpeg->audio_stream = stream_index;
ffmpeg->audio_st = ic->streams[stream_index];
ffmpeg->audio_buf_size = 0;
ffmpeg->audio_buf_index = 0;
memset(&ffmpeg->audio_pkt, 0, sizeof(ffmpeg->audio_pkt));
return 0;
}
static void stream_component_close(ffmpeg_t ffmpeg, int stream_index)
{
AVFormatContext *ic = ffmpeg->ctxt;
AVCodecContext *enc;
if (stream_index < 0 || stream_index >= (int)ic->nb_streams)
return;
enc = ic->streams[stream_index]->codec;
avcodec_close(enc);
}
/* Decode one audio frame and returns its uncompressed size */
static int audio_decode_frame(ffmpeg_t ffmpeg, uint8_t *audio_buf, int buf_size)
{
AVPacket *pkt = &ffmpeg->audio_pkt;
int len1, data_size;
for (;;) {
/* NOTE: the audio packet can contain several frames */
while (ffmpeg->audio_pkt_size > 0) {
data_size = buf_size;
len1 = avcodec_decode_audio2(ffmpeg->audio_st->codec,
(int16_t *)audio_buf, &data_size,
ffmpeg->audio_pkt_data, ffmpeg->audio_pkt_size);
if (len1 < 0) {
/* if error, we skip the frame */
ffmpeg->audio_pkt_size = 0;
break;
}
ffmpeg->audio_pkt_data += len1;
ffmpeg->audio_pkt_size -= len1;
if (data_size <= 0)
continue;
return data_size;
}
ffmpeg->audio_pkt_data = pkt->data;
ffmpeg->audio_pkt_size = pkt->size;
}
}
static int startread(sox_format_t * ft)
{
ffmpeg_t ffmpeg = (ffmpeg_t)ft->priv;
AVFormatParameters params;
int ret;
unsigned i;
ffmpeg->audio_buf = xcalloc(1, AVCODEC_MAX_AUDIO_FRAME_SIZE);
/* Signal audio stream not found */
ffmpeg->audio_index = -1;
/* register all CODECs, demux and protocols */
av_register_all();
/* Open file and get format */
memset(¶ms, 0, sizeof(params));
if ((ret = av_open_input_file(&ffmpeg->ctxt, ft->filename, NULL, 0, ¶ms)) < 0) {
sox_fail("ffmpeg cannot open file for reading: %s (code %d)", ft->filename, ret);
return SOX_EOF;
}
/* Get CODEC parameters */
if ((ret = av_find_stream_info(ffmpeg->ctxt)) < 0) {
sox_fail("ffmpeg could not find CODEC parameters for %s", ft->filename);
return SOX_EOF;
}
/* Now we can begin to play (RTSP stream only) */
av_read_play(ffmpeg->ctxt);
/* Find audio stream (FIXME: allow different stream to be selected) */
for (i = 0; i < ffmpeg->ctxt->nb_streams; i++) {
AVCodecContext *enc = ffmpeg->ctxt->streams[i]->codec;
if (enc->codec_type == CODEC_TYPE_AUDIO && ffmpeg->audio_index < 0) {
ffmpeg->audio_index = i;
break;
}
}
/* Open the stream */
if (ffmpeg->audio_index < 0 ||
stream_component_open(ffmpeg, ffmpeg->audio_index) < 0 ||
ffmpeg->audio_stream < 0) {
sox_fail("ffmpeg could not open CODECs for %s", ft->filename);
return SOX_EOF;
}
/* Copy format info */
ft->signal.rate = ffmpeg->audio_st->codec->sample_rate;
ft->signal.size = SOX_SIZE_16BIT;
ft->signal.encoding = SOX_ENCODING_SIGN2;
ft->signal.channels = ffmpeg->audio_st->codec->channels;
ft->length = 0; /* Currently we can't seek; no idea how to get this
info from ffmpeg anyway (in time, yes, but not in
samples); but ffmpeg *can* seek */
return SOX_SUCCESS;
}
/*
* Read up to len samples of type sox_sample_t from file into buf[].
* Return number of samples read.
*/
static sox_size_t read(sox_format_t * ft, sox_ssample_t *buf, sox_size_t len)
{
ffmpeg_t ffmpeg = (ffmpeg_t)ft->priv;
AVPacket *pkt = &ffmpeg->audio_pkt;
int ret;
sox_size_t nsamp = 0, nextra;
/* Read data repeatedly until buf is full or no more can be read */
do {
/* If input buffer empty, read more data */
if (ffmpeg->audio_buf_index * 2 >= ffmpeg->audio_buf_size) {
if ((ret = av_read_frame(ffmpeg->ctxt, pkt)) < 0)
break;
ffmpeg->audio_buf_size = audio_decode_frame(ffmpeg, ffmpeg->audio_buf, AVCODEC_MAX_AUDIO_FRAME_SIZE);
ffmpeg->audio_buf_index = 0;
}
/* Convert data into SoX samples up to size of buffer */
nextra = min((ffmpeg->audio_buf_size - ffmpeg->audio_buf_index) / 2, (int)(len - nsamp));
for (; nextra > 0; nextra--)
buf[nsamp++] = SOX_SIGNED_16BIT_TO_SAMPLE(((int16_t *)ffmpeg->audio_buf)[ffmpeg->audio_buf_index++], ft->clips);
} while (nsamp < len && nextra > 0);
return nsamp;
}
/*
* Close file for ffmpeg (this doesn't close the file handle)
*/
static int stopread(sox_format_t * ft)
{
ffmpeg_t ffmpeg = (ffmpeg_t)ft->priv;
if (ffmpeg->audio_stream >= 0)
stream_component_close(ffmpeg, ffmpeg->audio_stream);
if (ffmpeg->ctxt) {
av_close_input_file(ffmpeg->ctxt);
ffmpeg->ctxt = NULL; /* safety */
}
return SOX_SUCCESS;
}
/*
* add an audio output stream
*/
static AVStream *add_audio_stream(sox_format_t * ft, AVFormatContext *oc, enum CodecID codec_id)
{
AVCodecContext *c;
AVStream *st;
st = av_new_stream(oc, 1);
if (!st) {
sox_fail("ffmpeg could not alloc stream");
return NULL;
}
c = st->codec;
c->codec_id = codec_id;
c->codec_type = CODEC_TYPE_AUDIO;
/* put sample parameters */
c->bit_rate = 256000; /* FIXME: allow specification */
/* FIXME: currently mplayer says files do not have a specified
compressed bit-rate */
c->sample_rate = ft->signal.rate;
c->channels = ft->signal.channels;
return st;
}
static int open_audio(ffmpeg_t ffmpeg, AVStream *st)
{
AVCodecContext *c;
AVCodec *codec;
c = st->codec;
/* find the audio encoder */
codec = avcodec_find_encoder(c->codec_id);
if (!codec) {
sox_fail("ffmpeg CODEC not found");
return SOX_EOF;
}
/* open it */
if (avcodec_open(c, codec) < 0) {
sox_fail("ffmpeg could not open CODEC");
return SOX_EOF;
}
ffmpeg->audio_buf = xmalloc(AVCODEC_MAX_AUDIO_FRAME_SIZE);
/* ugly hack for PCM codecs (will be removed ASAP with new PCM
support to compute the input frame size in samples */
if (c->frame_size <= 1) {
ffmpeg->audio_input_frame_size = AVCODEC_MAX_AUDIO_FRAME_SIZE / c->channels;
switch(st->codec->codec_id) {
case CODEC_ID_PCM_S16LE:
case CODEC_ID_PCM_S16BE:
case CODEC_ID_PCM_U16LE:
case CODEC_ID_PCM_U16BE:
ffmpeg->audio_input_frame_size >>= 1;
break;
default:
break;
}
} else
ffmpeg->audio_input_frame_size = c->frame_size;
ffmpeg->samples = xmalloc((size_t)(ffmpeg->audio_input_frame_size * 2 * c->channels));
return SOX_SUCCESS;
}
static int startwrite(sox_format_t * ft)
{
ffmpeg_t ffmpeg = (ffmpeg_t)ft->priv;
/* initialize libavcodec, and register all codecs and formats */
av_register_all();
/* auto detect the output format from the name. default is
mpeg. */
ffmpeg->fmt = guess_format(NULL, ft->filename, NULL);
if (!ffmpeg->fmt) {
sox_warn("ffmpeg could not deduce output format from file extension; using MPEG");
ffmpeg->fmt = guess_format("mpeg", NULL, NULL);
if (!ffmpeg->fmt) {
sox_fail("ffmpeg could not find suitable output format");
return SOX_EOF;
}
}
/* allocate the output media context */
ffmpeg->ctxt = av_alloc_format_context();
if (!ffmpeg->ctxt) {
fprintf(stderr, "ffmpeg out of memory error");
return SOX_EOF;
}
ffmpeg->ctxt->oformat = ffmpeg->fmt;
snprintf(ffmpeg->ctxt->filename, sizeof(ffmpeg->ctxt->filename), "%s", ft->filename);
/* add the audio stream using the default format codecs
and initialize the codecs */
ffmpeg->audio_st = NULL;
if (ffmpeg->fmt->audio_codec != CODEC_ID_NONE) {
ffmpeg->audio_st = add_audio_stream(ft, ffmpeg->ctxt, ffmpeg->fmt->audio_codec);
if (ffmpeg->audio_st == NULL)
return SOX_EOF;
}
/* set the output parameters (must be done even if no
parameters). */
if (av_set_parameters(ffmpeg->ctxt, NULL) < 0) {
sox_fail("ffmpeg invalid output format parameters");
return SOX_EOF;
}
/* Next line for debugging */
/* dump_format(ffmpeg->ctxt, 0, ft->filename, 1); */
/* now that all the parameters are set, we can open the audio and
codec and allocate the necessary encode buffers */
if (ffmpeg->audio_st)
if (open_audio(ffmpeg, ffmpeg->audio_st) == SOX_EOF)
return SOX_EOF;
/* open the output file, if needed */
if (!(ffmpeg->fmt->flags & AVFMT_NOFILE)) {
if (url_fopen(&ffmpeg->ctxt->pb, ft->filename, URL_WRONLY) < 0) {
sox_fail("ffmpeg could not open `%s'", ft->filename);
return SOX_EOF;
}
}
/* write the stream header, if any */
av_write_header(ffmpeg->ctxt);
return SOX_SUCCESS;
}
/*
* Write up to len samples of type sox_sample_t from buf[] into file.
* Return number of samples written.
*/
static sox_size_t write(sox_format_t * ft, const sox_ssample_t *buf, sox_size_t len)
{
ffmpeg_t ffmpeg = (ffmpeg_t)ft->priv;
sox_size_t nread = 0, nwritten = 0;
/* Write data repeatedly until buf is empty */
do {
/* If output frame is not full, copy data into it */
if (ffmpeg->samples_index < ffmpeg->audio_input_frame_size) {
for (; nread < len && ffmpeg->samples_index < ffmpeg->audio_input_frame_size; nread++)
ffmpeg->samples[ffmpeg->samples_index++] = SOX_SAMPLE_TO_SIGNED_16BIT(buf[nread], ft->clips);
}
/* If output frame full or no more data to read, write it out */
if (ffmpeg->samples_index == ffmpeg->audio_input_frame_size ||
(len == 0 && ffmpeg->samples_index > 0)) {
AVCodecContext *c = ffmpeg->audio_st->codec;
AVPacket pkt;
av_init_packet(&pkt);
pkt.size = avcodec_encode_audio(c, ffmpeg->audio_buf, AVCODEC_MAX_AUDIO_FRAME_SIZE, ffmpeg->samples);
pkt.pts = av_rescale_q(c->coded_frame->pts, c->time_base, ffmpeg->audio_st->time_base);
pkt.flags |= PKT_FLAG_KEY;
pkt.stream_index = ffmpeg->audio_st->index;
pkt.data = ffmpeg->audio_buf;
/* write the compressed frame to the media file */
if (av_write_frame(ffmpeg->ctxt, &pkt) != 0)
sox_fail("ffmpeg had error while writing audio frame");
/* Increment nwritten whether write succeeded or not; we have to
get rid of the input! */
nwritten += ffmpeg->samples_index;
ffmpeg->samples_index = 0;
}
} while (nread < len);
return nwritten;
}
/*
* Close file for ffmpeg (this doesn't close the file handle)
*/
static int stopwrite(sox_format_t * ft)
{
ffmpeg_t ffmpeg = (ffmpeg_t)ft->priv;
unsigned i;
/* Close CODEC */
if (ffmpeg->audio_st) {
avcodec_close(ffmpeg->audio_st->codec);
free(ffmpeg->samples);
free(ffmpeg->audio_buf);
}
/* Write the trailer, if any */
av_write_trailer(ffmpeg->ctxt);
/* Free the streams */
for (i = 0; i < ffmpeg->ctxt->nb_streams; i++) {
av_freep(&ffmpeg->ctxt->streams[i]->codec);
av_freep(&ffmpeg->ctxt->streams[i]);
}
if (!(ffmpeg->fmt->flags & AVFMT_NOFILE)) {
/* close the output file */
url_fclose(&ffmpeg->ctxt->pb);
}
/* Free the output context */
av_free(ffmpeg->ctxt);
return SOX_SUCCESS;
}
/* Format file suffixes */
/* For now, comment out formats built in to SoX */
static const char *names[] = {
"ffmpeg", /* special type to force use of ffmpeg */
"mp4",
"m4a",
"avi",
"wmv",
"mpg",
NULL
};
/* Format descriptor */
static sox_format_handler_t sox_ffmpeg_format = {
names,
SOX_FILE_NOSTDIO,
startread,
read,
stopread,
startwrite,
write,
stopwrite,
sox_format_nothing_seek
};
const sox_format_handler_t *sox_ffmpeg_format_fn(void);
const sox_format_handler_t *sox_ffmpeg_format_fn(void)
{
return &sox_ffmpeg_format;
}