Add a new caf file demuxer module
authorMatthias Keiser <matthias@tristan-inc.com>
Mon, 30 Sep 2013 21:44:26 +0000 (23:44 +0200)
committerJean-Baptiste Kempf <jb@videolan.org>
Wed, 2 Oct 2013 13:45:43 +0000 (15:45 +0200)
NEWS
modules/MODULES_LIST
modules/demux/Makefile.am
modules/demux/caf.c [new file with mode: 0644]
po/POTFILES.in

diff --git a/NEWS b/NEWS
index 8efede9..888cf22 100644 (file)
--- a/NEWS
+++ b/NEWS
@@ -13,6 +13,9 @@ Encoder:
  * Support for HEVC/H.265 encoding using x265
  * Support for Opus encoding using libopus
 
+Demuxer:
+ * New CAF format module
+
 Streaming:
  * WebM streaming, including live sources, compatible with all major browsers
     vlc <your-source> vlc://quit --sout  '#transcode{vcodec=VP80,vb=1000,acodec=vorb,ab=128}
index 0493980..b2288aa 100644 (file)
@@ -1,4 +1,4 @@
-List of vlc plugins (413)
+List of vlc plugins (414)
 $Id$
  * a52: A/52 basic parser/packetizer
  * a52tofloat32: A/52 audio converter & decoder plugin, using liba52
@@ -55,6 +55,7 @@ $Id$
  * bluescreen: Bluescreen (weather channel like) video filter
  * bonjour: Zeroconf services discovery
  * caca: color ASCII art video output using libcaca
+ * caf: CAF demuxer
  * canvas: Automatically resize and padd a video
  * cc: CC 608/708 subtitles decoder
  * cdda: input module to read audio CDs
index 62a99c6..939d449 100644 (file)
@@ -126,6 +126,10 @@ demux_LTLIBRARIES += libasf_plugin.la
 libavi_plugin_la_SOURCES = demux/avi/avi.c demux/avi/libavi.c demux/avi/libavi.h
 demux_LTLIBRARIES += libavi_plugin.la
 
+libcaf_plugin_la_SOURCES = demux/caf.c
+libcaf_plugin_la_LIBADD = $(LIBM)
+demux_LTLIBRARIES += libcaf_plugin.la
+
 libavformat_plugin_la_SOURCES = demux/avformat/demux.c \
        codec/avcodec/fourcc.c \
        codec/avcodec/chroma.c \
diff --git a/modules/demux/caf.c b/modules/demux/caf.c
new file mode 100644 (file)
index 0000000..465caf6
--- /dev/null
@@ -0,0 +1,1059 @@
+/*****************************************************************************
+ * caf.c: Core Audio File Format demuxer
+ *****************************************************************************
+ * Copyright (C) 2013 VLC authors and VideoLAN
+ * $Id$
+ *
+ * Authors: Matthias Keiser <matthias@tristan-inc.com>
+ *
+ * This program 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 program 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 program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
+ *****************************************************************************/
+
+/*****************************************************************************
+ * Preamble
+ *****************************************************************************/
+
+#ifdef HAVE_CONFIG_H
+# include "config.h"
+#endif
+#include <math.h>
+#include <limits.h>
+#include <vlc_common.h>
+#include <vlc_plugin.h>
+#include <vlc_demux.h>
+#include <vlc_codecs.h>
+
+/* TODO:
+ *
+ * - handle channel layout
+ * - 64 bit float LPCM is broken (sound has artifacts with little endian, only silence plays for big endian).
+ */
+
+/*****************************************************************************
+ * Module descriptor
+ *****************************************************************************/
+static int  Open    ( vlc_object_t * );
+static void Close  ( vlc_object_t * );
+
+vlc_module_begin ()
+set_category( CAT_INPUT )
+set_subcategory( SUBCAT_INPUT_DEMUX )
+set_description( N_( "CAF demuxer" ))
+set_capability( "demux", 140 )
+set_callbacks( Open, Close )
+add_shortcut( "caf" )
+vlc_module_end ()
+
+/*****************************************************************************
+ * Local prototypes
+ *****************************************************************************/
+static int Demux  ( demux_t * );
+static int Control( demux_t *, int i_query, va_list args );
+
+typedef struct frame_span_t
+{
+    uint64_t i_frames;
+    uint64_t i_samples;
+    uint64_t i_bytes;
+    uint64_t i_desc_bytes;
+} frame_span_t;
+
+typedef struct packet_table_t
+{
+    uint64_t i_num_packets;
+    uint64_t i_num_valid_frames;
+    uint32_t i_num_priming_frames;
+    uint32_t i_num_remainder_frames;
+    uint64_t i_descriptions_start;
+} packet_table_t;
+
+struct demux_sys_t
+{
+    es_format_t  fmt;
+    es_out_id_t *es;
+
+    uint64_t i_data_offset;
+    uint64_t i_data_size;
+
+    frame_span_t position;
+    packet_table_t packet_table;
+};
+
+/*
+ We use this value to indicate that the data section extends until the end of the file.
+*/
+static const uint64_t kCHUNK_SIZE_EOF = UINT64_C( 0xffffffffffffffff );
+
+/*****************************************************************************
+ * Various Utility Functions
+ *****************************************************************************/
+
+/* ParseVarLenInteger parses a var length integer as found in the packet descriptions
+ (and in the aac magic cookie). In theorie a var length integer could be bigger than
+ an uint64_t, but I think it's unlikely to ever be a problem... */
+
+static int ParseVarLenInteger( const uint8_t *p_buff, size_t i_buff_len, uint64_t *pi_value_out, uint32_t *i_len_out )
+{
+    *i_len_out = 0;
+
+    uint64_t i_value = 0;
+    bool finished = false;
+
+    for( uint32_t i = 0; i < i_buff_len; i++ )
+    {
+        if( (( i_value >> 32 ) << 7 ) > UINT32_MAX )
+        {
+            return VLC_EGENERIC; /* overflow */
+        }
+        uint8_t i_byte = p_buff[i];
+        i_value = ( i_value << 7 ) | ( i_byte & 0x7f );
+
+        ( *i_len_out )++;
+
+        if( !( i_byte & 0x80 ))
+        {
+            finished = true;
+            break;
+        }
+    }
+
+    if( !finished )
+    {
+        return VLC_EGENERIC;
+    }
+
+    *pi_value_out = i_value;
+
+    return VLC_SUCCESS;
+}
+
+/* Utility function that reads a big endian double from the input buffer. */
+
+static inline double GetDBLBE( const uint8_t *p )
+{
+    union
+    {
+        uint64_t uint64;
+        double dbl;
+    } u_64;
+
+    u_64.uint64 = GetQWBE( p );
+    return u_64.dbl;
+}
+
+/* Utility function that reads a big endian signed 32 bit integer into an unsigned 32 bit variable.
+   If the read value is negative, this function returns an error.
+ */
+
+static inline int ReadBEInt32ToUInt32( const uint8_t *p, uint32_t *i_out )
+{
+    uint32_t i_value = GetDWBE( p );
+
+    if( i_value > INT32_MAX ) return VLC_EGENERIC;
+
+    *i_out = i_value;
+    return VLC_SUCCESS;
+}
+
+/* Utility function that reads a big endian signed 64 bit integer into an unsigned 64 bit variable.
+   If the read value is negative, this function returns an error.
+*/
+
+static inline int ReadBEInt64ToUInt64( const uint8_t *p, uint64_t *i_out )
+{
+    uint64_t i_value = GetQWBE( p );
+
+    if( i_value > INT64_MAX ) return VLC_EGENERIC;
+
+    *i_out = i_value;
+    return VLC_SUCCESS;
+}
+
+static inline bool NeedsPacketTable( demux_sys_t *p_sys )
+{
+    return ( !p_sys->fmt.audio.i_bytes_per_frame || !p_sys->fmt.audio.i_frame_length );
+}
+
+static uint64_t TotalNumFrames( demux_t *p_demux )
+{
+    demux_sys_t *p_sys = p_demux->p_sys;
+
+    if( !NeedsPacketTable( p_sys ))
+    {
+        uint64_t i_data_size;
+
+        if( p_sys->i_data_size != kCHUNK_SIZE_EOF)
+        {
+            i_data_size = p_sys->i_data_size;
+        }
+        else
+        {
+            int64_t i_stream_size = stream_Size( p_demux->s );
+            if(i_stream_size >= 0 && (uint64_t)i_stream_size >= p_sys->i_data_offset)
+                i_data_size = i_stream_size - p_sys->i_data_offset;
+            else
+                i_data_size = 0;
+        }
+
+        return i_data_size / p_sys->fmt.audio.i_bytes_per_frame;
+    }
+    else
+    {
+        return p_sys->packet_table.i_num_packets;
+    }
+}
+
+static uint64_t TotalNumSamples( demux_t *p_demux )
+{
+    demux_sys_t *p_sys = p_demux->p_sys;
+
+    if( !NeedsPacketTable( p_sys ))
+    {
+        return TotalNumFrames( p_demux ) * p_sys->fmt.audio.i_frame_length;
+    }
+    else
+    {
+        return p_sys->packet_table.i_num_valid_frames + p_sys->packet_table.i_num_priming_frames +
+                p_sys->packet_table.i_num_remainder_frames;
+    }
+}
+
+static inline vlc_fourcc_t ReadFOURCC( const uint8_t *p )
+{
+    return VLC_FOURCC( p[0], p[1], p[2], p[3] );
+}
+
+/*****************************************************************************
+ * FrameSpan Functions
+ *****************************************************************************
+ *  A FrameSpan structure contains the relationship between a number of
+ frames, the number of samples they contain, the amount of data they
+ use, and the length of their packet descriptions (if any).
+ *****************************************************************************/
+
+/* FrameSpanAddSpan adds span2 to span1 */
+
+static inline void FrameSpanAddSpan( frame_span_t *span1, frame_span_t *span2 )
+{
+    span1->i_frames += span2->i_frames;
+    span1->i_samples += span2->i_samples;
+    span1->i_bytes += span2->i_bytes;
+    span1->i_desc_bytes += span2->i_desc_bytes;
+}
+
+/* Adds the frame that is described at i_desc_offset to span */
+
+static int FrameSpanAddDescription( demux_t *p_demux, uint64_t i_desc_offset, frame_span_t *span )
+{
+    demux_sys_t *p_sys  = p_demux->p_sys;
+
+    /* Avoid seeking + peeking for simple case (PCM) */
+    if( p_sys->fmt.audio.i_bytes_per_frame && p_sys->fmt.audio.i_frame_length )
+    {
+        span->i_bytes += p_sys->fmt.audio.i_bytes_per_frame;
+        span->i_samples += p_sys->fmt.audio.i_frame_length;
+        span->i_frames++;
+        return VLC_SUCCESS;
+    }
+
+    uint32_t i_desc_size = 0;
+
+    if( stream_Seek( p_demux->s, p_sys->packet_table.i_descriptions_start + i_desc_offset ))
+    {
+        msg_Err( p_demux, "Couldn't seek packet description." );
+        return VLC_EGENERIC;
+    }
+
+    const uint8_t *p_peek;
+    uint32_t i_peek_len = stream_Peek( p_demux->s, &p_peek, 2 * 10 ); /* Peeking the maximum number of bytes that two 64 bit numbers could use (( 64 + 6 ) / 7 = 10 ). */
+
+    if( p_sys->fmt.audio.i_bytes_per_frame )
+    {
+        span->i_bytes += p_sys->fmt.audio.i_bytes_per_frame;
+    }
+    else
+    {
+        uint64_t i_size;
+        uint32_t i_this_int;
+        if( ParseVarLenInteger( p_peek, i_peek_len, &i_size, &i_this_int ))
+        {
+            return VLC_EGENERIC;
+        }
+
+        i_desc_size += i_this_int;
+        span->i_bytes += i_size;
+    }
+
+    if( p_sys->fmt.audio.i_frame_length )
+    {
+        span->i_samples += p_sys->fmt.audio.i_frame_length;
+    }
+    else
+    {
+        if( i_desc_size >= i_peek_len )
+        {
+            return VLC_EGENERIC;
+        }
+
+        uint64_t i_num_samples;
+        uint32_t i_this_int;
+        if( ParseVarLenInteger( p_peek + i_desc_size, i_peek_len - i_desc_size, &i_num_samples, &i_this_int ))
+        {
+            return VLC_EGENERIC;
+        }
+
+        i_desc_size += i_this_int;
+        span->i_samples += i_num_samples;
+    }
+    span->i_desc_bytes += i_desc_size;
+    span->i_frames++;
+
+    return VLC_SUCCESS;
+}
+
+/* FrameSpanGetTime returns the time span represented by the frame span. */
+
+static inline mtime_t FrameSpanGetTime( frame_span_t *span, uint32_t i_sample_rate )
+{
+    if( !i_sample_rate )
+        return 0;
+
+    return ( span->i_samples * CLOCK_FREQ ) / i_sample_rate + 1;
+}
+
+/* SetSpanWithSample returns the span from the beginning of the file up to and
+ including the specified sample. This works at frame granularity, so the
+ returned span may not represent the exact target sample.
+ Since we might have to lineraly search the packet descriptions, this is a
+ potentially slow operation! */
+
+static int SetSpanWithSample( demux_t *p_demux, frame_span_t *p_span, uint64_t i_target_sample )
+{
+    demux_sys_t *p_sys = p_demux->p_sys;
+
+    uint64_t i_num_frames = TotalNumFrames( p_demux );
+
+    if( !NeedsPacketTable( p_sys ))
+    {
+        uint64_t i_frame = i_target_sample / p_sys->fmt.audio.i_frame_length;
+        uint64_t i_remaining = i_target_sample - i_frame * p_sys->fmt.audio.i_frame_length;
+        if( i_remaining > ( p_sys->fmt.audio.i_frame_length / 2 ))
+            i_frame++;
+
+        if( i_frame > i_num_frames )
+            i_frame = i_num_frames;
+
+        p_span->i_frames = i_frame;
+        p_span->i_samples = i_frame * p_sys->fmt.audio.i_frame_length;
+        p_span->i_bytes = i_frame * p_sys->fmt.audio.i_bytes_per_frame;
+        p_span->i_desc_bytes = 0;
+    }
+    else
+    {
+        *p_span = (frame_span_t){0};
+        frame_span_t prev_span;
+
+        while( p_span->i_samples < i_target_sample && p_span->i_frames < i_num_frames )
+        {
+            prev_span = *p_span;
+
+            if( FrameSpanAddDescription( p_demux, p_span->i_desc_bytes, p_span ))
+                return VLC_EGENERIC;
+
+            if( p_span->i_samples >= i_target_sample )
+            {
+                uint64_t i_this_samples = p_span->i_samples - prev_span.i_samples;
+
+                if( i_target_sample - prev_span.i_samples < i_this_samples / 2 )
+                    *p_span = prev_span;
+
+                break;
+            }
+        }
+    }
+
+    return VLC_SUCCESS;
+}
+
+/*****************************************************************************
+ * CAF Parsing Functions
+ *****************************************************************************/
+
+/* The NextChunk function returns the four char code and the (sanitized) size at the current file position.
+   Upon return the file position points to the start of the chunk payload.
+   Note that the ReadXXXChunk functions expect the chunk to size to be sanitized
+   (i.e. they don't check if it is bigger than INT64_MAX, but note the special case of the 'data' chunk, which can be kCHUNK_SIZE_EOF).
+ */
+
+static int NextChunk( demux_t *p_demux, vlc_fourcc_t *p_fcc, uint64_t *pi_size )
+{
+    uint8_t p_read[12];
+
+    if( stream_Read( p_demux->s, p_read, 12 ) < 12 )
+        return VLC_EGENERIC;
+
+    *p_fcc = ReadFOURCC( p_read );
+    uint64_t i_size = GetQWBE( p_read + 4 );
+
+    /* We accept no negativ sizes for chunks, except -1 for the data chunk. */
+
+    if( i_size > INT64_MAX )
+    {
+        if( *p_fcc == VLC_FOURCC( 'd', 'a', 't', 'a' ) && i_size == UINT64_C( -1 ))
+            i_size = kCHUNK_SIZE_EOF;
+        else
+            return VLC_EGENERIC;
+    }
+
+    *pi_size = i_size;
+
+    return VLC_SUCCESS;
+}
+
+static int ReadDescChunk( demux_t *p_demux )
+{
+    demux_sys_t *p_sys = p_demux->p_sys;
+
+    const uint8_t *p_peek;
+
+    if ( stream_Peek( p_demux->s, &p_peek, 8 + 6 * 4 ) < ( 8 + 6 * 4 ))
+    {
+        return VLC_EGENERIC;
+    }
+
+    vlc_fourcc_t i_fmt = ReadFOURCC( p_peek + 8 );
+    uint32_t i_fmt_flags = GetDWBE( p_peek + 12 );
+
+    uint32_t i_bits_per_channel = GetDWBE( p_peek + 28 );
+    uint32_t i_bytes_per_packet = GetDWBE( p_peek + 16 );
+    uint32_t i_frames_per_packet = GetDWBE( p_peek + 20 );
+    uint32_t i_channels_per_frame = GetDWBE( p_peek + 24 );
+
+    if( i_fmt == VLC_CODEC_DVD_LPCM )
+    {
+        if( !i_frames_per_packet || !i_channels_per_frame )
+        {
+            msg_Err( p_demux, "Absurd LPCM parameters (frames_per_packet: %u, channels_per_frame: %u).", i_frames_per_packet, i_channels_per_frame );
+            return VLC_EGENERIC;
+        }
+
+        uint32_t i_unpacked_bits_per_sample = ( i_bytes_per_packet / i_frames_per_packet / i_channels_per_frame ) * 8;
+
+        bool b_is_float = !!( i_fmt_flags & ( 1 << 0 ) );
+        bool b_is_be = !( i_fmt_flags & ( 1 << 1 ) );
+
+        vlc_fourcc_t i_basic_codec = 0;
+
+        if( !b_is_float )
+        {
+            i_basic_codec = b_is_be ? VLC_FOURCC( 't', 'w', 'o', 's' ) : VLC_FOURCC( 's', 'o', 'w', 't' );
+            es_format_Init( &p_sys->fmt, AUDIO_ES, vlc_fourcc_GetCodecAudio( i_basic_codec, i_unpacked_bits_per_sample ));
+        }
+        else
+        {
+            if( i_bits_per_channel == 32 )
+                i_basic_codec = b_is_be ? VLC_CODEC_F32B : VLC_CODEC_F32L;
+            else if( i_bits_per_channel == 64 )
+                i_basic_codec = b_is_be ? VLC_CODEC_F64B : VLC_CODEC_F64L;
+
+            if( i_basic_codec )
+                es_format_Init( &p_sys->fmt, AUDIO_ES, vlc_fourcc_GetCodecAudio( i_basic_codec, i_bits_per_channel ));
+        }
+    }
+    else if( i_fmt == VLC_FOURCC( 'a', 'a', 'c', ' ' ))
+    {
+        const uint32_t kMP4Audio_AAC_LC_ObjectType = 2;
+
+        if( i_fmt_flags != kMP4Audio_AAC_LC_ObjectType )
+        {
+            msg_Warn( p_demux, "The only documented format flag for aac is 2 (kMP4Audio_AAC_LC_ObjectType), but is %i. Continuing anyways.", i_fmt_flags );
+        }
+
+        es_format_Init( &p_sys->fmt, AUDIO_ES, vlc_fourcc_GetCodecAudio( VLC_CODEC_MP4A, 0 ));
+
+    }
+    else
+    {
+        es_format_Init( &p_sys->fmt, AUDIO_ES, vlc_fourcc_GetCodecAudio( i_fmt, 0 ));
+    }
+
+    if( !p_sys->fmt.i_codec )
+    {
+        msg_Err( p_demux, "could not determine codec" );
+        return VLC_EGENERIC;
+    }
+
+    double d_rate = round( GetDBLBE( p_peek ));
+
+    if( d_rate <= 0 || d_rate > UINT_MAX )
+        return VLC_EGENERIC;
+
+    p_sys->fmt.audio.i_rate = (unsigned int)lround( d_rate );
+    p_sys->fmt.audio.i_channels = i_channels_per_frame;
+    p_sys->fmt.audio.i_bytes_per_frame = i_bytes_per_packet; /* "mBytesPerPacket" in Apple parlance */
+    p_sys->fmt.audio.i_frame_length = i_frames_per_packet; /* "mFramesPerPacket" in Apple parlance */
+    p_sys->fmt.audio.i_bitspersample = i_bits_per_channel; /* mBitsPerChannel */
+    p_sys->fmt.audio.i_blockalign = i_bytes_per_packet;
+    p_sys->fmt.i_bitrate = i_bits_per_channel * p_sys->fmt.audio.i_rate * i_channels_per_frame;
+
+    return VLC_SUCCESS;
+}
+
+/*  This is lifted from cafdec.c in libavformat (function read_kuki_chunk). Appearantly the
+    alac library expects the cookie to be of length 36, but current alac files
+    have a cookie length of 24.
+ */
+static int ProcessALACCookie( demux_t *p_demux, const uint8_t *p, uint64_t i_size )
+{
+    demux_sys_t *p_sys = p_demux->p_sys;
+
+    const unsigned int kALAC_NEW_KUKI_SIZE = 24;
+    const unsigned int kALAC_LIB_REQ_KUKI_SIZE = 36;
+    int i_extra;
+
+    if( i_size == kALAC_NEW_KUKI_SIZE || i_size == kALAC_LIB_REQ_KUKI_SIZE )
+    {
+        i_extra = kALAC_LIB_REQ_KUKI_SIZE;
+    }
+    else
+    {
+        msg_Warn( p_demux, "Unknown alac magic cookie. Passing it on to the decoder as is and hoping for the best." );
+        i_extra = ( int )i_size;
+    }
+
+    p_sys->fmt.i_extra = i_extra;
+    p_sys->fmt.p_extra = malloc( i_extra );
+
+    if( !p_sys->fmt.p_extra )
+        return VLC_ENOMEM;
+
+    uint8_t *p_extra = ( uint8_t * )p_sys->fmt.p_extra;
+
+    if( i_size == kALAC_NEW_KUKI_SIZE )
+    {
+        SetDWBE( p_extra, 36 );
+        memcpy( p_extra + 4, "alac", 4 );
+        SetDWBE( p_extra + 8, 0 );
+        memcpy( p_extra + 12, p, 24 );
+    }
+    else
+    {
+        memcpy( p_sys->fmt.p_extra, p, i_size );
+    }
+
+    return VLC_SUCCESS;
+}
+
+/* Helper functions for AAC cookie processing. */
+
+static inline bool AACCookieGetTag( uint8_t *p_tag, const uint8_t *p, uint64_t *p_offset, uint64_t i_size )
+{
+    if( *p_offset + 1 > i_size )
+        return false;
+
+    *p_tag = *( p + *p_offset );
+    *p_offset += 1;
+
+    return true;
+}
+
+static inline bool AACCookieTagLen( uint64_t *p_tag_len, const uint8_t *p, uint64_t *p_offset, uint64_t i_size )
+{
+    uint32_t i_int_size;
+
+    if( ParseVarLenInteger( p + *p_offset, i_size - *p_offset, p_tag_len, &i_int_size ))
+        return false;
+
+    *p_offset += i_int_size;
+
+    return true;
+}
+
+static inline bool AACCookieChkLen( int64_t i_length, uint64_t i_size, uint64_t i_offset )
+{
+    return ( i_offset + i_length <= i_size );
+}
+
+/*  This is lifted from libmp4.c in the mp4 demuxer module (function MP4_ReadBox_esds). The aac
+    library only want a subset of the magic cookie ("decoder specific info"), so we have to parse it.
+ */
+static int ProcessAACCookie( demux_t *p_demux, const uint8_t *p, uint64_t i_size )
+{
+    const uint8_t kAAC_ES_DESCR_TAG = 3;
+    const uint8_t kAAC_DEC_CONFIG_DESCR_TAG = 4;
+    const uint8_t kAAC_DEC_SPEC_INFO_TAG = 5;
+
+    demux_sys_t *p_sys = p_demux->p_sys;
+
+    uint64_t i_offset = 0;
+    uint64_t i_kuki_size = 0;
+    uint64_t i_tag_len;
+    uint8_t i_tag;
+
+    if( !AACCookieGetTag( &i_tag, p, &i_offset, i_size )) goto aac_kuki_finish;
+
+    if( i_tag == kAAC_ES_DESCR_TAG )
+    {
+
+        if( !AACCookieTagLen( &i_tag_len, p, &i_offset, i_size )) goto aac_kuki_finish;
+
+        if( !AACCookieChkLen( 3, i_size, i_offset )) goto aac_kuki_finish;
+        i_offset += 2; /* don't care (ES ID) */
+        uint8_t i_flags = *( p + i_offset++ );
+
+        if( i_flags&0x80 )
+        {
+            if( !AACCookieChkLen( 2, i_size, i_offset )) goto aac_kuki_finish;
+            i_offset += 2; /* don't care (dependance) */
+        }
+        if( i_flags&0x40 )
+        {
+            if( !AACCookieChkLen( 1, i_size, i_offset )) goto aac_kuki_finish;
+            uint8_t i_url_len = *( p + i_offset++ );
+            i_offset += i_url_len; /* don't care (url) */
+        }
+        if( i_flags&0x20 )
+        {
+            if( !AACCookieChkLen( 2, i_size, i_offset )) goto aac_kuki_finish;
+            i_offset += 2; /* don't care (OCR) */
+        }
+
+        if( !AACCookieGetTag( &i_tag, p, &i_offset, i_size )) goto aac_kuki_finish;
+    }
+
+    if( i_tag != kAAC_DEC_CONFIG_DESCR_TAG )
+        goto aac_kuki_finish;
+
+    if( !AACCookieTagLen( &i_tag_len, p, &i_offset, i_size )) goto aac_kuki_finish;
+
+    if( !AACCookieChkLen( 1 + 1 + 3 + 4 + 4, i_size, i_offset )) goto aac_kuki_finish;
+    i_offset += ( 1 + 1 + 3 + 4 + 4 ); /* don't care */
+
+    if( !AACCookieGetTag( &i_tag, p, &i_offset, i_size )) goto aac_kuki_finish;
+
+    if( i_tag != kAAC_DEC_SPEC_INFO_TAG ) /* this is the one we need */
+        goto aac_kuki_finish;
+
+    if( !AACCookieTagLen( &i_tag_len, p, &i_offset, i_size )) goto aac_kuki_finish;
+
+    if( i_offset + i_tag_len > i_size )
+        goto aac_kuki_finish;
+
+    i_kuki_size = i_tag_len;
+
+aac_kuki_finish:
+
+    if( !i_kuki_size )
+    {
+        msg_Warn( p_demux, "Error parsing aac cookie. Passing it on to the decoder as is and hoping for the best." );
+        i_kuki_size = i_size;
+        i_offset = 0;
+    }
+
+    p_sys->fmt.i_extra = (int)i_kuki_size;
+    p_sys->fmt.p_extra = malloc( i_kuki_size );
+
+    if( !p_sys->fmt.p_extra )
+    {
+        return VLC_ENOMEM;
+    }
+
+    memcpy( p_sys->fmt.p_extra, p + i_offset, i_kuki_size );
+
+    return VLC_SUCCESS;
+}
+
+static int ReadKukiChunk( demux_t *p_demux, uint64_t i_size )
+{
+    demux_sys_t *p_sys = p_demux->p_sys;
+    const uint8_t *p_peek;
+
+    /* stream_Peek can't handle sizes bigger than INT32_MAX, and also p_sys->fmt.i_extra is of type 'int'*/
+    if( i_size > INT32_MAX )
+    {
+        msg_Err( p_demux, "Magic Cookie chunk too big" );
+        return VLC_EGENERIC;
+    }
+
+    if( (unsigned int)stream_Peek( p_demux->s, &p_peek, (int)i_size ) < i_size )
+    {
+        msg_Err( p_demux, "Couldn't peek extra data" );
+        return VLC_EGENERIC;
+    }
+
+    if( p_sys->fmt.i_codec  == VLC_CODEC_ALAC )
+    {
+        int error = ProcessALACCookie( p_demux, p_peek, i_size );
+        if( error ) return error;
+    }
+    else if( p_sys->fmt.i_codec == VLC_CODEC_MP4A )
+    {
+        int error = ProcessAACCookie( p_demux, p_peek, i_size );
+        if( error ) return error;
+    }
+    else
+    {
+        p_sys->fmt.i_extra = (int)i_size;
+        p_sys->fmt.p_extra = malloc( i_size );
+
+        if( !p_sys->fmt.p_extra )
+        {
+            return VLC_ENOMEM;
+        }
+        memcpy( p_sys->fmt.p_extra, p_peek, p_sys->fmt.i_extra );
+    }
+
+    return VLC_SUCCESS;
+}
+
+static int ReadDataChunk( demux_t *p_demux, uint64_t i_size )
+{
+    if( i_size < 4 )
+        return VLC_EGENERIC;
+
+    demux_sys_t *p_sys = p_demux->p_sys;
+
+    p_sys->i_data_offset = stream_Tell( p_demux->s ) + 4; /* skip edit count */
+    p_sys->i_data_size = i_size == kCHUNK_SIZE_EOF ? kCHUNK_SIZE_EOF : ( i_size - 4 );
+
+    return VLC_SUCCESS;
+}
+
+static int ReadPaktChunk( demux_t *p_demux )
+{
+    demux_sys_t *p_sys = p_demux->p_sys;
+
+    const uint8_t *p_peek;
+
+    if ( stream_Peek( p_demux->s, &p_peek, 8 + 8 + 4 + 4 ) < ( 8 + 8 + 4 + 4 ))
+    {
+        msg_Err( p_demux, "Couldn't peek packet descriptions" );
+        return VLC_EGENERIC;
+    }
+
+    if( ReadBEInt64ToUInt64( p_peek, &p_sys->packet_table.i_num_packets ))
+    {
+        msg_Err( p_demux, "Invalid packet table: i_num_packets is negative.");
+        return VLC_EGENERIC;
+    }
+    if( ReadBEInt64ToUInt64( p_peek + 8, &p_sys->packet_table.i_num_valid_frames ))
+    {
+        msg_Err( p_demux, "Invalid packet table: i_num_valid_frames is negative.");
+        return VLC_EGENERIC;
+    }
+    if( ReadBEInt32ToUInt32( p_peek + 16, &p_sys->packet_table.i_num_priming_frames ))
+    {
+        msg_Err( p_demux, "Invalid packet table: i_num_priming_frames is negative.");
+        return VLC_EGENERIC;
+    }
+    if( ReadBEInt32ToUInt32( p_peek + 20, &p_sys->packet_table.i_num_remainder_frames ))
+    {
+        msg_Err( p_demux, "Invalid packet table: i_num_remainder_frames is negative.");
+        return VLC_EGENERIC;
+    }
+
+    p_sys->packet_table.i_descriptions_start = stream_Tell( p_demux->s ) + 24;
+
+    return VLC_SUCCESS;
+}
+
+/*****************************************************************************
+ * Open
+ *****************************************************************************/
+static int Open( vlc_object_t *p_this )
+{
+    int i_error = VLC_SUCCESS;
+
+    demux_t     *p_demux = (demux_t*)p_this;
+    demux_sys_t *p_sys;
+
+    const uint8_t *p_peek;
+
+    if( stream_Peek( p_demux->s, &p_peek, 8 ) < 8 )
+        return VLC_EGENERIC;
+
+    /* Is it a caf file? */
+    if( memcmp( p_peek, "caff", 4 ))
+        return VLC_EGENERIC;
+
+    /* check file version (we only handle version 1) */
+    uint16_t i_version = GetWBE( p_peek + 4 );
+    if( i_version != 1 )
+    {
+        msg_Dbg( p_demux, "Unknown caf file version %d.", i_version );
+        return VLC_EGENERIC;
+    }
+
+    /* check file flags (must be 0) */
+    uint16_t i_flags = GetWBE( p_peek + 6 );
+    if( i_flags != 0 )
+    {
+        msg_Dbg( p_demux, "Unknown caf file flags %d.", i_flags );
+        return VLC_EGENERIC;
+    }
+
+    if( stream_Read( p_demux->s, NULL, 8 ) < 8 )
+        return VLC_EGENERIC; /* This would be very strange since we justed peeked at these bytes. */
+
+    p_demux->p_sys = calloc( 1, sizeof( demux_sys_t ));
+    if( !p_demux->p_sys ) return VLC_ENOMEM;
+
+    /* From this point on, we have to free p_sys if we return an error (e.g. "goto caf_open_end") */
+
+    p_sys = p_demux->p_sys;
+    es_format_Init( &p_sys->fmt, UNKNOWN_ES, 0 );
+
+    vlc_fourcc_t i_fcc;
+    uint64_t i_size;
+    uint64_t i_idx = 0;
+
+    while( NextChunk( p_demux, &i_fcc, &i_size ) == VLC_SUCCESS )
+    {
+        bool b_handled = true;
+
+        switch ( i_fcc )
+        {
+            case VLC_FOURCC( 'd', 'e', 's', 'c' ):
+
+                if( i_idx != 0 )
+                {
+                    msg_Err( p_demux, "The audio description chunk must be the first chunk in a caf file." );
+                    i_error = VLC_EGENERIC;
+                    goto caf_open_end;
+                }
+
+                i_error = ReadDescChunk( p_demux );
+                break;
+
+            case VLC_FOURCC( 'd', 'a', 't', 'a' ):
+
+                i_error = ReadDataChunk( p_demux, i_size );
+                break;
+
+            case VLC_FOURCC( 'p', 'a', 'k', 't' ):
+
+                i_error = ReadPaktChunk( p_demux );
+                break;
+
+            case VLC_FOURCC( 'k', 'u', 'k', 'i' ):
+
+                i_error = ReadKukiChunk( p_demux, i_size );
+                break;
+
+            default:
+
+                b_handled = false;
+                break;
+        }
+
+        if( i_error )
+            goto caf_open_end;
+
+        if( b_handled )
+            msg_Dbg( p_demux, "Found '%4.4s' chunk.", ( char * )&i_fcc );
+        else
+            msg_Dbg( p_demux, "Ignoring '%4.4s' chunk.", ( char * )&i_fcc );
+
+        if( i_size == kCHUNK_SIZE_EOF )
+            break;
+
+        if( stream_Seek( p_demux->s, stream_Tell( p_demux->s ) + i_size ) != VLC_SUCCESS )
+            break;
+
+        i_idx++;
+    }
+
+    if ( !p_sys->i_data_offset || p_sys->fmt.i_cat != AUDIO_ES ||
+        ( NeedsPacketTable( p_sys ) && !p_sys->packet_table.i_descriptions_start ))
+    {
+        msg_Err( p_demux, "Did not find all necessary chunks." );
+        i_error = VLC_EGENERIC;
+        goto caf_open_end;
+    }
+
+    p_sys->es = es_out_Add( p_demux->out, &p_sys->fmt );
+
+    if( !p_sys->es )
+    {
+        msg_Err( p_demux, "Could not add elementary stream." );
+        i_error = VLC_EGENERIC;
+        goto caf_open_end;
+    }
+
+caf_open_end:
+
+    if( i_error )
+    {
+        free( p_sys->fmt.p_extra );
+        free( p_sys  );
+
+        if( stream_Seek( p_demux->s, 0 ))
+        {
+            msg_Warn(p_demux, "Could not reset stream position to 0.");
+        }
+    }
+    else
+    {
+        p_demux->pf_control = Control;
+        p_demux->pf_demux = Demux;
+    }
+
+    return i_error;
+}
+
+/*****************************************************************************
+ * Demux:
+ *****************************************************************************/
+static int Demux( demux_t *p_demux )
+{
+    demux_sys_t *p_sys = p_demux->p_sys;
+    block_t     *p_block;
+
+    if( p_sys->i_data_size != kCHUNK_SIZE_EOF && p_sys->position.i_bytes >= p_sys->i_data_size )
+    {
+        /* EOF */
+        return 0;
+    }
+
+    frame_span_t advance = (frame_span_t){0};
+
+    /* we will read 50ms at once */
+    uint64_t i_req_samples = __MAX( p_sys->fmt.audio.i_rate / 20, 1 );
+
+    if( !NeedsPacketTable( p_sys )) /* PCM/IMA4 */
+    {
+        int64_t i_req_frames = ( i_req_samples + ( p_sys->fmt.audio.i_frame_length - 1 )) / p_sys->fmt.audio.i_frame_length;
+
+        if( p_sys->i_data_size != kCHUNK_SIZE_EOF && ( p_sys->position.i_bytes + i_req_frames * p_sys->fmt.audio.i_bytes_per_frame ) > p_sys->i_data_size )
+        {
+            i_req_frames = ( p_sys->i_data_size - p_sys->position.i_frames * p_sys->fmt.audio.i_bytes_per_frame ) / p_sys->fmt.audio.i_bytes_per_frame;
+        }
+
+        advance.i_frames = i_req_frames;
+        advance.i_samples = i_req_frames * p_sys->fmt.audio.i_frame_length;
+        advance.i_bytes = p_sys->fmt.audio.i_bytes_per_frame * advance.i_frames;
+    }
+    else /* use packet table */
+    {
+        do
+        {
+            if( FrameSpanAddDescription( p_demux, p_sys->position.i_desc_bytes + advance.i_desc_bytes, &advance ))
+                break;
+        }
+        while (( i_req_samples > advance.i_samples ) && ( p_sys->position.i_frames + advance.i_frames ) < p_sys->packet_table.i_num_packets );
+    }
+
+    if( !advance.i_frames )
+    {
+        msg_Err( p_demux, "Unexpected end of file" );
+        return -1;
+    }
+
+    if( stream_Seek( p_demux->s, p_sys->i_data_offset + p_sys->position.i_bytes ))
+    {
+        if( p_sys->i_data_size == kCHUNK_SIZE_EOF)
+            return 0;
+
+        msg_Err( p_demux, "cannot seek data" );
+        return -1;
+    }
+
+    if(( p_block = stream_Block( p_demux->s, (int)advance.i_bytes )) == NULL )
+    {
+        msg_Err( p_demux, "cannot read data" );
+        return -1;
+    }
+
+    p_block->i_dts =
+    p_block->i_pts = VLC_TS_0 + FrameSpanGetTime( &p_sys->position, p_sys->fmt.audio.i_rate );
+
+    FrameSpanAddSpan( &p_sys->position, &advance );
+
+    /* set PCR */
+    es_out_Control( p_demux->out, ES_OUT_SET_PCR, p_block->i_pts );
+
+    es_out_Send( p_demux->out, p_sys->es, p_block );
+
+    return 1;
+}
+
+/*****************************************************************************
+ * Control:
+ *****************************************************************************/
+static int Control( demux_t *p_demux, int i_query, va_list args )
+{
+    int64_t i64, *pi64, i_sample;
+    double f, *pf;
+    frame_span_t position;
+
+    demux_sys_t *p_sys  = p_demux->p_sys;
+    uint64_t i_num_samples = TotalNumSamples( p_demux );
+
+    switch( i_query )
+    {
+        case DEMUX_GET_LENGTH:
+            pi64 = ( int64_t* )va_arg( args, int64_t * );
+            *pi64 = CLOCK_FREQ * ( i_num_samples / p_sys->fmt.audio.i_rate );
+            return VLC_SUCCESS;
+
+        case DEMUX_GET_TIME:
+            pi64 = ( int64_t* )va_arg( args, int64_t * );
+            *pi64 = CLOCK_FREQ * ( p_sys->position.i_samples / p_sys->fmt.audio.i_rate );
+            return VLC_SUCCESS;
+
+        case DEMUX_GET_POSITION:
+            pf = (double*)va_arg( args, double * );
+            *pf = i_num_samples ? (double)p_sys->position.i_samples / (double)i_num_samples : 0.0;
+            return VLC_SUCCESS;
+
+        case DEMUX_SET_POSITION:
+            f = (double)va_arg( args, double );
+            i_sample = f * i_num_samples;
+            if( SetSpanWithSample( p_demux, &position, i_sample ))
+                return VLC_EGENERIC;
+            p_sys->position = position;
+            return VLC_SUCCESS;
+
+        case DEMUX_SET_TIME:
+            i64 = (int64_t)va_arg( args, int64_t );
+            i_sample = i64 * p_sys->fmt.audio.i_rate / INT64_C( 1000000 );
+            if( SetSpanWithSample( p_demux, &position, i_sample ))
+                return VLC_EGENERIC;
+            p_sys->position = position;
+            return VLC_SUCCESS;
+
+        case DEMUX_GET_META:
+            return stream_Control( p_demux->s, STREAM_GET_META, args );
+
+        default:
+            return VLC_EGENERIC;
+    }
+
+    return VLC_EGENERIC;
+}
+
+/*****************************************************************************
+ * Close
+ *****************************************************************************/
+static void Close( vlc_object_t *p_this )
+{
+    demux_t     *p_demux = (demux_t*)p_this;
+    demux_sys_t *p_sys = p_demux->p_sys;
+
+    es_out_Del( p_demux->out, p_sys->es );
+
+    free( p_sys->fmt.p_extra );
+    free( p_sys );
+}
index 13f831c..e66e8ee 100644 (file)
@@ -441,6 +441,7 @@ modules/demux/avformat/mux.c
 modules/demux/avi/avi.c
 modules/demux/avi/libavi.c
 modules/demux/avi/libavi.h
+modules/demux/caf.c
 modules/demux/cdg.c
 modules/demux/demuxdump.c
 modules/demux/dirac.c