shithub: sox

Download patch

ref: 1bc7b8a7728977addd313236b31e9e075d75cdf1
parent: 2502700854968797aa93f2d63e6f37c54359d774
author: rrt <rrt>
date: Sat Apr 14 16:33:46 EDT 2007

Add write support for ffmpeg.

--- a/README
+++ b/README
@@ -36,10 +36,9 @@
   o Amiga MAUD files
   o AMR-WB files
   o MP3 (with optional libmad and libmp3lame libraries)
-  o MP4, AAC, AC3, WAVPACK files (read-only, with optional ffmpeg
+  o MP4, AAC, AC3, WAVPACK files (with optional ffmpeg library)
+  o AVI, WMV, Ogg Theora, MPEG video files (with optional ffmpeg
     library)
-  o AVI, WMV, Ogg Theora, MPEG video files (read-only, with optional
-    ffmpeg library)
   o Ogg Vorbis files (with optional Ogg Vorbis libraries)
   o FLAC files (with optional libflac)
   o IRCAM SoundFile files
--- a/sox.1
+++ b/sox.1
@@ -901,13 +901,13 @@
 .BR .paf .
 .TP
 .B ffmpeg
-This is a pseudo-type that forces ffmpeg to be used. At the moment,
-ffmpeg support is read-only. The actual file type is deduced from the
-file. This pseudo-type depends on SoX having been built with optional
-ffmpeg support. It can read a wide range of audio files, not all of
-which are documented here, and also the audio track of many video
-files (including AVI, WMV and MPEG). At present only the first audio
-track of a file can be read.
+This is a pseudo-type that forces ffmpeg to be used. The actual file
+type is deduced from the file name (it cannot be used on stdio). This
+pseudo-type depends on SoX having been built with optional ffmpeg
+support. It can read a wide range of audio files, not all of which are
+documented here, and also the audio track of many video files
+(including AVI, WMV and MPEG). At present only the first audio track
+of a file can be read.
 .TP
 .B .flac (also with \-t sndfile)
 Free Lossless Audio CODEC compressed audio.
@@ -1005,8 +1005,7 @@
 .SP
 MP3 support in
 SoX is optional and requires access to either or both the external
-libmad and libmp3lame libraries.  To
-see if there is support for MP3 run
+libmad and libmp3lame libraries. To see if there is support for MP3 run
 .EX
 	sox -h
 .EE
@@ -1020,7 +1019,7 @@
 for more information.
 .SP
 MP4 support in SoX is optional and requires access to the external
-ffmpeg libraries. Currently it is read-only.
+ffmpeg libraries.
 .TP
 .B .nist (also with \-t sndfile)
 See \fB.sph\fR.
--- a/src/ffmpeg.c
+++ b/src/ffmpeg.c
@@ -3,8 +3,29 @@
  *
  * Copyright 2007 Reuben Thomas <rrt@sc3d.org>
  *
- * Based on ffplay.c Copyright 2003 Fabrice Bellard
+ * 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
@@ -33,13 +54,16 @@
 /* Private data for ffmpeg files */
 typedef struct ffmpeg
 {
-  uint8_t *audio_buf;
   int audio_index;
   int audio_stream;
   AVStream *audio_st;
-  AVFormatContext *ic;
-  int audio_buf_index;
-  int audio_buf_size;
+  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;
@@ -51,7 +75,7 @@
 /* open a given stream. Return 0 if OK */
 static int stream_component_open(ffmpeg_t ffmpeg, int stream_index)
 {
-  AVFormatContext *ic = ffmpeg->ic;
+  AVFormatContext *ic = ffmpeg->ctxt;
   AVCodecContext *enc;
   AVCodec *codec;
 
@@ -65,7 +89,7 @@
 
   codec = avcodec_find_decoder(enc->codec_id);
   enc->workaround_bugs = 1;
-  enc->error_resilience= 1;
+  enc->error_resilience = 1;
   if (!codec || avcodec_open(enc, codec) < 0)
     return -1;
   if (enc->codec_type != CODEC_TYPE_AUDIO) {
@@ -85,7 +109,7 @@
 
 static void stream_component_close(ffmpeg_t ffmpeg, int stream_index)
 {
-  AVFormatContext *ic = ffmpeg->ic;
+  AVFormatContext *ic = ffmpeg->ctxt;
   AVCodecContext *enc;
 
   if (stream_index < 0 || stream_index >= (int)ic->nb_streams)
@@ -95,13 +119,13 @@
   avcodec_close(enc);
 }
 
-/* decode one audio frame and returns its uncompressed size */
+/* 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(;;) {
+  for (;;) {
     /* NOTE: the audio packet can contain several frames */
     while (ffmpeg->audio_pkt_size > 0) {
       data_size = buf_size;
@@ -126,10 +150,7 @@
   }
 }
 
-/*
- * Open file in ffmpeg.
- */
-static int sox_ffmpeg_startread(ft_t ft)
+static int startread(ft_t ft)
 {
   ffmpeg_t ffmpeg = (ffmpeg_t)ft->priv;
   AVFormatParameters params;
@@ -146,23 +167,23 @@
 
   /* Open file and get format */
   memset(&params, 0, sizeof(params));
-  if ((ret = av_open_input_file(&ffmpeg->ic, ft->filename, NULL, 0, &params)) < 0) {
+  if ((ret = av_open_input_file(&ffmpeg->ctxt, ft->filename, NULL, 0, &params)) < 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->ic)) < 0) {
+  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->ic);
+  av_read_play(ffmpeg->ctxt);
 
-  /* Find audio stream (assume we're using the first) */
-  for (i = 0; i < ffmpeg->ic->nb_streams; i++) {
-    AVCodecContext *enc = ffmpeg->ic->streams[i]->codec;
+  /* 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;
@@ -173,7 +194,7 @@
   if (ffmpeg->audio_index < 0 ||
       stream_component_open(ffmpeg, ffmpeg->audio_index) < 0 ||
       ffmpeg->audio_stream < 0) {
-    sox_fail("could not open CODECs for %s", ft->filename);
+    sox_fail("ffmpeg could not open CODECs for %s", ft->filename);
     return SOX_EOF;
   }
 
@@ -193,7 +214,7 @@
  * Read up to len samples of type sox_sample_t from file into buf[].
  * Return number of samples read.
  */
-static sox_size_t sox_ffmpeg_read(ft_t ft, sox_ssample_t *buf, sox_size_t len)
+static sox_size_t read(ft_t ft, sox_ssample_t *buf, sox_size_t len)
 {
   ffmpeg_t ffmpeg = (ffmpeg_t)ft->priv;
   AVPacket *pkt = &ffmpeg->audio_pkt;
@@ -202,17 +223,18 @@
 
   /* Read data repeatedly until buf is full or no more can be read */
   do {
-    /* If buffer empty, read more data */
+    /* If input buffer empty, read more data */
     if (ffmpeg->audio_buf_index * 2 >= ffmpeg->audio_buf_size) {
-      if ((ret = av_read_frame(ffmpeg->ic, pkt)) < 0)
+      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++],);
+      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;
@@ -221,27 +243,238 @@
 /*
  * Close file for ffmpeg (this doesn't close the file handle)
  */
-static int sox_ffmpeg_stopread(ft_t ft)
+static int stopread(ft_t ft)
 {
   ffmpeg_t ffmpeg = (ffmpeg_t)ft->priv;
 
   if (ffmpeg->audio_stream >= 0)
     stream_component_close(ffmpeg, ffmpeg->audio_stream);
-  if (ffmpeg->ic) {
-    av_close_input_file(ffmpeg->ic);
-    ffmpeg->ic = NULL; /* safety */
+  if (ffmpeg->ctxt) {
+    av_close_input_file(ffmpeg->ctxt);
+    ffmpeg->ctxt = NULL; /* safety */
   }
   
   return SOX_SUCCESS;
 }
 
-static int sox_ffmpeg_startwrite(ft_t ft)
+/*
+ * add an audio output stream
+ */
+static AVStream *add_audio_stream(ft_t ft, AVFormatContext *oc, enum CodecID codec_id)
 {
-  (void)ft;
-  sox_fail("Cannot (yet) write with ffmpeg");
-  return SOX_EOF;
+  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(ft_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(ft_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(ft_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[] = {
@@ -258,12 +491,12 @@
 static sox_format_t sox_ffmpeg_format = {
   names,
   SOX_FILE_NOSTDIO,
-  sox_ffmpeg_startread,
-  sox_ffmpeg_read,
-  sox_ffmpeg_stopread,
-  sox_ffmpeg_startwrite,
-  sox_format_nothing_write,
-  sox_format_nothing,
+  startread,
+  read,
+  stopread,
+  startwrite,
+  write,
+  stopwrite,
   sox_format_nothing_seek
 };