about summary refs log tree commit homepage
diff options
context:
space:
mode:
authorEric Wong <normalperson@yhbt.net>2013-08-25 12:03:11 +0000
committerEric Wong <normalperson@yhbt.net>2013-08-25 12:27:33 +0000
commit963d0b4be91e1050ba947a9bbddd511f78321c54 (patch)
tree7b0f8b0211b0dd96838873285ae5593f398aa8dd
parent0f9bfb784b7bbccf86b7d7d241982ba4e703efd4 (diff)
downloaddtas-963d0b4be91e1050ba947a9bbddd511f78321c54.tar.gz
We need to tell sox to use and resample to the _player_ format
instead of the source format.  Otherwise 48000 Hz audio (common for
video?) sounds very slow when attempting to play back on the default
44100 Hz.  It is also likely preferable to choose the audio stream
which matches the player channel count instead of letting sox
automatically invoke the remix effect.

Before this change, playing 6-channel, 48000 Hz source into a sink
expecting stereo, 44100 Hz is rather disturbing...

While we're at it, clarify the spawn code for the sox source, too.

We can drop the test_format_from_file test now, since
it's probably overkill at this point.
-rw-r--r--lib/dtas/source/av.rb49
-rw-r--r--lib/dtas/source/sox.rb4
-rw-r--r--test/test_source_av.rb13
3 files changed, 38 insertions, 28 deletions
diff --git a/lib/dtas/source/av.rb b/lib/dtas/source/av.rb
index 804a66b..1de0a72 100644
--- a/lib/dtas/source/av.rb
+++ b/lib/dtas/source/av.rb
@@ -11,9 +11,12 @@ class DTAS::Source::Av # :nodoc:
 
   include DTAS::Source::File
 
+  AStream = Struct.new(:duration, :channels, :rate)
+
   AV_DEFAULTS = COMMAND_DEFAULTS.merge(
     "command" =>
-      'avconv -v error $SSPOS -i "$INFILE" -f sox - | sox -p $SOXFMT - $RGFX',
+      'avconv -v error $SSPOS -i "$INFILE" $AMAP -f sox - |' \
+      'sox -p $SOXFMT - $RGFX',
     "comments" => nil,
   )
 
@@ -42,24 +45,23 @@ class DTAS::Source::Av # :nodoc:
     @comments = {}
     err = ""
     s = qx(%W(avprobe -show_streams -show_format #@infile), err: err)
+    @astreams = []
     s.scan(%r{^\[STREAM\]\n(.*?)\n\[/STREAM\]\n}m) do |_|
       stream = $1
       # XXX what to do about multiple streams?
       if stream =~ /^codec_type=audio$/
-        duration = channels = rate = nil
-        stream =~ /^duration=([\d\.]+)\s*$/m and duration = $1.to_f
-        stream =~ /^channels=(\d)\s*$/m and channels = $1.to_i
-        stream =~ /^sample_rate=([\d\.]+)\s*$/m and rate = $1.to_i
-        if channels > 0 && rate > 0
-          @duration = duration
-          @format.channels = channels
-          @format.rate = rate
-        end
+        as = AStream.new
+        index = nil
+        stream =~ /^index=(\d+)\s*$/m and index = $1.to_i
+        stream =~ /^duration=([\d\.]+)\s*$/m and as.duration = $1.to_f
+        stream =~ /^channels=(\d)\s*$/m and as.channels = $1.to_i
+        stream =~ /^sample_rate=([\d\.]+)\s*$/m and as.rate = $1.to_i
+        @astreams[index] = as if as.channels > 0 && as.rate > 0
       end
     end
     s.scan(%r{^\[FORMAT\]\n(.*?)\n\[/FORMAT\]\n}m) do |_|
       f = $1
-      f =~ /^duration=([\d\.]+)\s*$/m and @duration ||= $1.to_f
+      f =~ /^duration=([\d\.]+)\s*$/m and @duration = $1.to_f
       # TODO: multi-line/multi-value/repeated tags
       f.gsub!(/^TAG:([^=]+)=(.*)$/i) { |_| @comments[$1.upcase] = $2 }
     end
@@ -71,10 +73,31 @@ class DTAS::Source::Av # :nodoc:
     sprintf("-ss %0.9g", samples / @format.rate)
   end
 
-  def spawn(format, rg_state, opts)
+  def spawn(player_format, rg_state, opts)
     raise "BUG: #{self.inspect}#spawn called twice" if @to_io
-    e = @format.to_env
+    amap = nil
+    found_as = nil
+
+    # try to find an audio stream which matches our channel count
+    # we need to set @format for sspos() down below
+    @astreams.each_with_index do |as, index|
+      if as && as.channels == player_format.channels
+        @format.channels = as.channels
+        @format.rate = as.rate
+        found_as = as
+        amap = "-map 0:#{index}"
+      end
+    end
+    unless found_as
+      first_as = @astreams.compact[0]
+      if first_as
+        @format.channels = found_as.channels
+        @format.rate = found_as.rate
+      end
+    end
+    e = player_format.to_env
     e["INFILE"] = @infile
+    e["AMAP"] = amap
 
     # make sure these are visible to the "current" command...
     @env["SSPOS"] = @offset ? sspos(@offset) : nil
diff --git a/lib/dtas/source/sox.rb b/lib/dtas/source/sox.rb
index 954c1a5..235cbbc 100644
--- a/lib/dtas/source/sox.rb
+++ b/lib/dtas/source/sox.rb
@@ -86,9 +86,9 @@ class DTAS::Source::Sox # :nodoc:
     tmp
   end
 
-  def spawn(format, rg_state, opts)
+  def spawn(player_format, rg_state, opts)
     raise "BUG: #{self.inspect}#spawn called twice" if @to_io
-    e = format.to_env
+    e = player_format.to_env
     e["INFILE"] = @infile
 
     # make sure these are visible to the "current" command...
diff --git a/test/test_source_av.rb b/test/test_source_av.rb
index 2104b7e..4dba559 100644
--- a/test/test_source_av.rb
+++ b/test/test_source_av.rb
@@ -98,17 +98,4 @@ class TestSourceAv < Minitest::Unit::TestCase
     source = DTAS::Source::Av.new(tmp.path, '1')
     assert_equal 1000000.0, source.offset_us
   end
-
-  def test_format_from_file
-    Tempfile.open(%w(tmp .wav)) do |tmp|
-      # generate an empty file with 1s of audio
-      cmd = %W(sox -r 96000 -b 24 -c 2 -n #{tmp.path} trim 0 1)
-      system(*cmd)
-      assert $?.success?, "#{cmd.inspect} failed: #$?"
-      fmt = DTAS::Source::Av.new(tmp.path).format
-      assert_equal 96000, fmt.rate
-      assert_equal 2, fmt.channels
-      tmp.unlink
-    end
-  end
 end