]> code.delx.au - pulseaudio/blobdiff - src/pulsecore/sound-file-stream.c
Merge branch 'master' of git://0pointer.de/pulseaudio
[pulseaudio] / src / pulsecore / sound-file-stream.c
index e0d51a7d1b97d2a663a3dbb40e779b365b4c1e1f..16de492332bd1c89cea7b0e959f0fd0ed4956097 100644 (file)
@@ -1,13 +1,11 @@
-/* $Id$ */
-
 /***
   This file is part of PulseAudio.
 
 /***
   This file is part of PulseAudio.
 
-  Copyright 2004-2006 Lennart Poettering
+  Copyright 2004-2008 Lennart Poettering
 
   PulseAudio is free software; you can redistribute it and/or modify
   it under the terms of the GNU Lesser General Public License as published
 
   PulseAudio 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 of the License,
+  by the Free Software Foundation; either version 2.1 of the License,
   or (at your option) any later version.
 
   PulseAudio is distributed in the hope that it will be useful, but
   or (at your option) any later version.
 
   PulseAudio is distributed in the hope that it will be useful, but
 #include <sndfile.h>
 
 #include <pulse/xmalloc.h>
 #include <sndfile.h>
 
 #include <pulse/xmalloc.h>
+#include <pulse/util.h>
 
 #include <pulsecore/core-error.h>
 #include <pulsecore/sink-input.h>
 #include <pulsecore/log.h>
 #include <pulsecore/thread-mq.h>
 
 #include <pulsecore/core-error.h>
 #include <pulsecore/sink-input.h>
 #include <pulsecore/log.h>
 #include <pulsecore/thread-mq.h>
+#include <pulsecore/core-util.h>
+#include <pulsecore/sample-util.h>
+#include <pulsecore/sndfile-util.h>
 
 #include "sound-file-stream.h"
 
 
 #include "sound-file-stream.h"
 
-#define BUF_SIZE (1024*16)
+#define MEMBLOCKQ_MAXLENGTH (16*1024*1024)
 
 typedef struct file_stream {
     pa_msgobject parent;
     pa_core *core;
 
 typedef struct file_stream {
     pa_msgobject parent;
     pa_core *core;
-    SNDFILE *sndfile;
     pa_sink_input *sink_input;
     pa_sink_input *sink_input;
-    pa_memchunk memchunk;
+
+    SNDFILE *sndfile;
     sf_count_t (*readf_function)(SNDFILE *sndfile, void *ptr, sf_count_t frames);
     sf_count_t (*readf_function)(SNDFILE *sndfile, void *ptr, sf_count_t frames);
-    size_t drop;
+
+    /* We need this memblockq here to easily fulfill rewind requests
+     * (even beyond the file start!) */
+    pa_memblockq *memblockq;
 } file_stream;
 
 enum {
     FILE_STREAM_MESSAGE_UNLINK
 };
 
 } file_stream;
 
 enum {
     FILE_STREAM_MESSAGE_UNLINK
 };
 
-PA_DECLARE_CLASS(file_stream);
+PA_DEFINE_PRIVATE_CLASS(file_stream, pa_msgobject);
 #define FILE_STREAM(o) (file_stream_cast(o))
 #define FILE_STREAM(o) (file_stream_cast(o))
-static PA_DEFINE_CHECK_TYPE(file_stream, pa_msgobject);
 
 
+/* Called from main context */
 static void file_stream_unlink(file_stream *u) {
     pa_assert(u);
 
     if (!u->sink_input)
         return;
 static void file_stream_unlink(file_stream *u) {
     pa_assert(u);
 
     if (!u->sink_input)
         return;
-    
-    pa_sink_input_disconnect(u->sink_input);
-    
+
+    pa_sink_input_unlink(u->sink_input);
     pa_sink_input_unref(u->sink_input);
     u->sink_input = NULL;
     pa_sink_input_unref(u->sink_input);
     u->sink_input = NULL;
-    
+
     /* Make sure we don't decrease the ref count twice. */
     file_stream_unref(u);
 }
 
     /* Make sure we don't decrease the ref count twice. */
     file_stream_unref(u);
 }
 
+/* Called from main context */
 static void file_stream_free(pa_object *o) {
     file_stream *u = FILE_STREAM(o);
     pa_assert(u);
 
 static void file_stream_free(pa_object *o) {
     file_stream *u = FILE_STREAM(o);
     pa_assert(u);
 
-    file_stream_unlink(u);
-    
-    if (u->memchunk.memblock)
-        pa_memblock_unref(u->memchunk.memblock);
+    if (u->memblockq)
+        pa_memblockq_free(u->memblockq);
 
     if (u->sndfile)
         sf_close(u->sndfile);
 
     if (u->sndfile)
         sf_close(u->sndfile);
@@ -93,10 +96,11 @@ static void file_stream_free(pa_object *o) {
     pa_xfree(u);
 }
 
     pa_xfree(u);
 }
 
+/* Called from main context */
 static int file_stream_process_msg(pa_msgobject *o, int code, void*userdata, int64_t offset, pa_memchunk *chunk) {
     file_stream *u = FILE_STREAM(o);
     file_stream_assert_ref(u);
 static int file_stream_process_msg(pa_msgobject *o, int code, void*userdata, int64_t offset, pa_memchunk *chunk) {
     file_stream *u = FILE_STREAM(o);
     file_stream_assert_ref(u);
-    
+
     switch (code) {
         case FILE_STREAM_MESSAGE_UNLINK:
             file_stream_unlink(u);
     switch (code) {
         case FILE_STREAM_MESSAGE_UNLINK:
             file_stream_unlink(u);
@@ -106,117 +110,122 @@ static int file_stream_process_msg(pa_msgobject *o, int code, void*userdata, int
     return 0;
 }
 
     return 0;
 }
 
+/* Called from main context */
 static void sink_input_kill_cb(pa_sink_input *i) {
 static void sink_input_kill_cb(pa_sink_input *i) {
+    file_stream *u;
+
+    pa_sink_input_assert_ref(i);
+    u = FILE_STREAM(i->userdata);
+    file_stream_assert_ref(u);
+
+    file_stream_unlink(u);
+}
+
+/* Called from IO thread context */
+static void sink_input_state_change_cb(pa_sink_input *i, pa_sink_input_state_t state) {
+    file_stream *u;
+
     pa_sink_input_assert_ref(i);
     pa_sink_input_assert_ref(i);
-    
-    file_stream_unlink(FILE_STREAM(i->userdata));
+    u = FILE_STREAM(i->userdata);
+    file_stream_assert_ref(u);
+
+    /* If we are added for the first time, ask for a rewinding so that
+     * we are heard right-away. */
+    if (PA_SINK_INPUT_IS_LINKED(state) &&
+        i->thread_info.state == PA_SINK_INPUT_INIT)
+        pa_sink_input_request_rewind(i, 0, FALSE, TRUE, TRUE);
 }
 
 }
 
-static int sink_input_peek_cb(pa_sink_input *i, pa_memchunk *chunk) {
+/* Called from IO thread context */
+static int sink_input_pop_cb(pa_sink_input *i, size_t length, pa_memchunk *chunk) {
     file_stream *u;
     file_stream *u;
-    
-    pa_assert(i);
+
+    pa_sink_input_assert_ref(i);
     pa_assert(chunk);
     u = FILE_STREAM(i->userdata);
     file_stream_assert_ref(u);
 
     pa_assert(chunk);
     u = FILE_STREAM(i->userdata);
     file_stream_assert_ref(u);
 
-    if (!u->sndfile)
+    if (!u->memblockq)
         return -1;
         return -1;
-    
+
     for (;;) {
     for (;;) {
-        
-        if (!u->memchunk.memblock) {
-            
-            u->memchunk.memblock = pa_memblock_new(i->sink->core->mempool, BUF_SIZE);
-            u->memchunk.index = 0;
-            
-            if (u->readf_function) {
-                sf_count_t n;
-                void *p;
-                size_t fs = pa_frame_size(&i->sample_spec);
-                
-                p = pa_memblock_acquire(u->memchunk.memblock);
-                n = u->readf_function(u->sndfile, p, BUF_SIZE/fs);
-                pa_memblock_release(u->memchunk.memblock);
-
-                if (n <= 0)
-                    n = 0;
-                
-                u->memchunk.length = n * fs;
-            } else {
-                sf_count_t n;
-                void *p;
-
-                p = pa_memblock_acquire(u->memchunk.memblock);
-                n = sf_read_raw(u->sndfile, p, BUF_SIZE);
-                pa_memblock_release(u->memchunk.memblock);
-                
-                if (n <= 0)
-                    n = 0;
-                
-                u->memchunk.length = n;
-            }
-            
-            if (u->memchunk.length <= 0) {
-
-                pa_memblock_unref(u->memchunk.memblock);
-                pa_memchunk_reset(&u->memchunk);
-                
-                pa_asyncmsgq_post(pa_thread_mq_get()->outq, PA_MSGOBJECT(u), FILE_STREAM_MESSAGE_UNLINK, NULL, 0, NULL, NULL);
-
-                sf_close(u->sndfile);
-                u->sndfile = NULL;
-                
-                return -1;
-            }
+        pa_memchunk tchunk;
+        size_t fs;
+        void *p;
+        sf_count_t n;
+
+        if (pa_memblockq_peek(u->memblockq, chunk) >= 0) {
+            chunk->length = PA_MIN(chunk->length, length);
+            pa_memblockq_drop(u->memblockq, chunk->length);
+            return 0;
         }
 
         }
 
-        pa_assert(u->memchunk.memblock);
-        pa_assert(u->memchunk.length > 0);
+        if (!u->sndfile)
+            break;
+
+        tchunk.memblock = pa_memblock_new(i->sink->core->mempool, length);
+        tchunk.index = 0;
+
+        p = pa_memblock_acquire(tchunk.memblock);
 
 
-        if (u->drop < u->memchunk.length) {
-            u->memchunk.index += u->drop;
-            u->memchunk.length -= u->drop;
-            u->drop = 0;
+        if (u->readf_function) {
+            fs = pa_frame_size(&i->sample_spec);
+            n = u->readf_function(u->sndfile, p, (sf_count_t) (length/fs));
+        } else {
+            fs = 1;
+            n = sf_read_raw(u->sndfile, p, (sf_count_t) length);
+        }
+
+        pa_memblock_release(tchunk.memblock);
+
+        if (n <= 0) {
+            pa_memblock_unref(tchunk.memblock);
+
+            sf_close(u->sndfile);
+            u->sndfile = NULL;
             break;
         }
             break;
         }
-                
-        u->drop -= u->memchunk.length;
-        pa_memblock_unref(u->memchunk.memblock);
-        pa_memchunk_reset(&u->memchunk);
+
+        tchunk.length = (size_t) n * fs;
+
+        pa_memblockq_push(u->memblockq, &tchunk);
+        pa_memblock_unref(tchunk.memblock);
     }
 
     }
 
-    *chunk = u->memchunk;
-    pa_memblock_ref(chunk->memblock);
-    
-    pa_assert(chunk->length > 0);
-    pa_assert(u->drop <= 0);
-    
-    return 0;
+    if (pa_sink_input_safe_to_remove(i)) {
+        pa_memblockq_free(u->memblockq);
+        u->memblockq = NULL;
+
+        pa_asyncmsgq_post(pa_thread_mq_get()->outq, PA_MSGOBJECT(u), FILE_STREAM_MESSAGE_UNLINK, NULL, 0, NULL, NULL);
+    }
+
+    return -1;
+ }
+
+static void sink_input_process_rewind_cb(pa_sink_input *i, size_t nbytes) {
+    file_stream *u;
+
+    pa_sink_input_assert_ref(i);
+    u = FILE_STREAM(i->userdata);
+    file_stream_assert_ref(u);
+
+    if (!u->memblockq)
+        return;
+
+    pa_memblockq_rewind(u->memblockq, nbytes);
 }
 
 }
 
-static void sink_input_drop_cb(pa_sink_input *i, size_t length) {
+static void sink_input_update_max_rewind_cb(pa_sink_input *i, size_t nbytes) {
     file_stream *u;
 
     file_stream *u;
 
-    pa_assert(i);
-    pa_assert(length > 0);
+    pa_sink_input_assert_ref(i);
     u = FILE_STREAM(i->userdata);
     file_stream_assert_ref(u);
     u = FILE_STREAM(i->userdata);
     file_stream_assert_ref(u);
-    
-    if (u->memchunk.memblock) {
 
 
-        if (length < u->memchunk.length) {
-            u->memchunk.index += length;
-            u->memchunk.length -= length;
-            return;
-        }
+    if (!u->memblockq)
+        return;
 
 
-        length -= u->memchunk.length;
-        pa_memblock_unref(u->memchunk.memblock);
-        pa_memchunk_reset(&u->memchunk);
-    }
-            
-    u->drop += length;
+    pa_memblockq_set_maxrewind(u->memblockq, nbytes);
 }
 
 int pa_play_file(
 }
 
 int pa_play_file(
@@ -225,11 +234,12 @@ int pa_play_file(
         const pa_cvolume *volume) {
 
     file_stream *u = NULL;
         const pa_cvolume *volume) {
 
     file_stream *u = NULL;
-    SF_INFO sfinfo;
     pa_sample_spec ss;
     pa_sample_spec ss;
+    pa_channel_map cm;
     pa_sink_input_new_data data;
     int fd;
     pa_sink_input_new_data data;
     int fd;
-    
+    SF_INFO sfi;
+
     pa_assert(sink);
     pa_assert(fname);
 
     pa_assert(sink);
     pa_assert(fname);
 
@@ -238,14 +248,15 @@ int pa_play_file(
     u->parent.process_msg = file_stream_process_msg;
     u->core = sink->core;
     u->sink_input = NULL;
     u->parent.process_msg = file_stream_process_msg;
     u->core = sink->core;
     u->sink_input = NULL;
-    pa_memchunk_reset(&u->memchunk);
     u->sndfile = NULL;
     u->readf_function = NULL;
     u->sndfile = NULL;
     u->readf_function = NULL;
-    u->drop = 0;
-
-    memset(&sfinfo, 0, sizeof(sfinfo));
+    u->memblockq = NULL;
 
 
-    if ((fd = open(fname, O_RDONLY|O_NOCTTY)) < 0) {
+    if ((fd = open(fname, O_RDONLY
+#ifdef O_NOCTTY
+                   |O_NOCTTY
+#endif
+                   )) < 0) {
         pa_log("Failed to open file %s: %s", fname, pa_cstrerror(errno));
         goto fail;
     }
         pa_log("Failed to open file %s: %s", fname, pa_cstrerror(errno));
         goto fail;
     }
@@ -255,80 +266,78 @@ int pa_play_file(
      * file reader into the main event loop and pass the data over the
      * asyncmsgq. */
 
      * file reader into the main event loop and pass the data over the
      * asyncmsgq. */
 
+#ifdef HAVE_POSIX_FADVISE
     if (posix_fadvise(fd, 0, 0, POSIX_FADV_SEQUENTIAL) < 0) {
         pa_log_warn("POSIX_FADV_SEQUENTIAL failed: %s", pa_cstrerror(errno));
         goto fail;
     } else
         pa_log_debug("POSIX_FADV_SEQUENTIAL succeeded.");
     if (posix_fadvise(fd, 0, 0, POSIX_FADV_SEQUENTIAL) < 0) {
         pa_log_warn("POSIX_FADV_SEQUENTIAL failed: %s", pa_cstrerror(errno));
         goto fail;
     } else
         pa_log_debug("POSIX_FADV_SEQUENTIAL succeeded.");
-    
+
     if (posix_fadvise(fd, 0, 0, POSIX_FADV_WILLNEED) < 0) {
         pa_log_warn("POSIX_FADV_WILLNEED failed: %s", pa_cstrerror(errno));
         goto fail;
     } else
         pa_log_debug("POSIX_FADV_WILLNEED succeeded.");
     if (posix_fadvise(fd, 0, 0, POSIX_FADV_WILLNEED) < 0) {
         pa_log_warn("POSIX_FADV_WILLNEED failed: %s", pa_cstrerror(errno));
         goto fail;
     } else
         pa_log_debug("POSIX_FADV_WILLNEED succeeded.");
-    
-    if (!(u->sndfile = sf_open_fd(fd, SFM_READ, &sfinfo, 1))) {
+#endif
+
+    pa_zero(sfi);
+    if (!(u->sndfile = sf_open_fd(fd, SFM_READ, &sfi, 1))) {
         pa_log("Failed to open file %s", fname);
         pa_log("Failed to open file %s", fname);
-        close(fd);
         goto fail;
     }
 
         goto fail;
     }
 
-    switch (sfinfo.format & 0xFF) {
-        case SF_FORMAT_PCM_16:
-        case SF_FORMAT_PCM_U8:
-        case SF_FORMAT_PCM_S8:
-            ss.format = PA_SAMPLE_S16NE;
-            u->readf_function = (sf_count_t (*)(SNDFILE *sndfile, void *ptr, sf_count_t frames)) sf_readf_short;
-            break;
-
-        case SF_FORMAT_ULAW:
-            ss.format = PA_SAMPLE_ULAW;
-            break;
+    fd = -1;
 
 
-        case SF_FORMAT_ALAW:
-            ss.format = PA_SAMPLE_ALAW;
-            break;
-
-        case SF_FORMAT_FLOAT:
-        default:
-            ss.format = PA_SAMPLE_FLOAT32NE;
-            u->readf_function = (sf_count_t (*)(SNDFILE *sndfile, void *ptr, sf_count_t frames)) sf_readf_float;
-            break;
+    if (pa_sndfile_read_sample_spec(u->sndfile, &ss) < 0) {
+        pa_log("Failed to determine file sample format.");
+        goto fail;
     }
 
     }
 
-    ss.rate = sfinfo.samplerate;
-    ss.channels = sfinfo.channels;
-
-    if (!pa_sample_spec_valid(&ss)) {
-        pa_log("Unsupported sample format in file %s", fname);
-        goto fail;
+    if (pa_sndfile_read_channel_map(u->sndfile, &cm) < 0) {
+        if (ss.channels > 2)
+            pa_log_info("Failed to determine file channel map, synthesizing one.");
+        pa_channel_map_init_extend(&cm, ss.channels, PA_CHANNEL_MAP_DEFAULT);
     }
 
     }
 
+    u->readf_function = pa_sndfile_readf_function(&ss);
+
     pa_sink_input_new_data_init(&data);
     data.sink = sink;
     data.driver = __FILE__;
     pa_sink_input_new_data_init(&data);
     data.sink = sink;
     data.driver = __FILE__;
-    data.name = fname;
     pa_sink_input_new_data_set_sample_spec(&data, &ss);
     pa_sink_input_new_data_set_sample_spec(&data, &ss);
+    pa_sink_input_new_data_set_channel_map(&data, &cm);
     pa_sink_input_new_data_set_volume(&data, volume);
     pa_sink_input_new_data_set_volume(&data, volume);
+    pa_proplist_sets(data.proplist, PA_PROP_MEDIA_NAME, pa_path_get_filename(fname));
+    pa_proplist_sets(data.proplist, PA_PROP_MEDIA_FILENAME, fname);
+    pa_sndfile_init_proplist(u->sndfile, data.proplist);
 
 
-    if (!(u->sink_input = pa_sink_input_new(sink->core, &data, 0)))
+    pa_sink_input_new(&u->sink_input, sink->core, &data);
+    pa_sink_input_new_data_done(&data);
+
+    if (!u->sink_input)
         goto fail;
 
         goto fail;
 
-    u->sink_input->peek = sink_input_peek_cb;
-    u->sink_input->drop = sink_input_drop_cb;
+    u->sink_input->pop = sink_input_pop_cb;
+    u->sink_input->process_rewind = sink_input_process_rewind_cb;
+    u->sink_input->update_max_rewind = sink_input_update_max_rewind_cb;
     u->sink_input->kill = sink_input_kill_cb;
     u->sink_input->kill = sink_input_kill_cb;
+    u->sink_input->state_change = sink_input_state_change_cb;
     u->sink_input->userdata = u;
 
     u->sink_input->userdata = u;
 
+    u->memblockq = pa_memblockq_new(0, MEMBLOCKQ_MAXLENGTH, 0, pa_frame_size(&ss), 1, 1, 0, NULL);
+
     pa_sink_input_put(u->sink_input);
 
     /* The reference to u is dangling here, because we want to keep
      * this stream around until it is fully played. */
     pa_sink_input_put(u->sink_input);
 
     /* The reference to u is dangling here, because we want to keep
      * this stream around until it is fully played. */
-    
+
     return 0;
 
 fail:
     return 0;
 
 fail:
-    if (u)
-        file_stream_unref(u);
+    file_stream_unref(u);
+
+    if (fd >= 0)
+        pa_close(fd);
 
     return -1;
 }
 
     return -1;
 }