Class: RockboxFFI::Decoder

Inherits:
Object
  • Object
show all
Defined in:
lib/rockbox_ffi/decoder.rb

Overview

Decode a local audio file to interleaved-stereo S16 PCM, one chunk at a time, using the Rockbox codec engine.

The codec state is process-wide, so only one Decoder may decode at a time and it must be used from one thread. Call #close when done, or use the block form Decoder.open(path) { |dec| ... }.

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(path) ⇒ Decoder

Returns a new instance of Decoder.



26
27
28
29
30
31
32
# File 'lib/rockbox_ffi/decoder.rb', line 26

def initialize(path)
  @ptr = Lib.rb_decoder_open(path.to_s)
  raise "could not open decoder for #{path.inspect}" if @ptr.null?

  @finalizer = self.class.send(:finalizer, @ptr)
  ObjectSpace.define_finalizer(self, @finalizer)
end

Class Method Details

.open(path) ⇒ Object

Open a Decoder for path; if a block is given, close it automatically afterwards.



15
16
17
18
19
20
21
22
23
24
# File 'lib/rockbox_ffi/decoder.rb', line 15

def self.open(path)
  dec = new(path)
  return dec unless block_given?

  begin
    yield dec
  ensure
    dec.close
  end
end

Instance Method Details

#closeObject

-- lifecycle --------------------------------------------------------



35
36
37
38
39
40
41
# File 'lib/rockbox_ffi/decoder.rb', line 35

def close
  return if @ptr.nil?

  ObjectSpace.undefine_finalizer(self)
  Lib.rb_decoder_free(@ptr)
  @ptr = nil
end

#closed?Boolean

Returns:

  • (Boolean)


43
44
45
# File 'lib/rockbox_ffi/decoder.rb', line 43

def closed?
  @ptr.nil?
end

#each_chunkObject

Yield each [samples, sample_rate] chunk until end of track. Returns an Enumerator when no block is given.



85
86
87
88
89
90
91
# File 'lib/rockbox_ffi/decoder.rb', line 85

def each_chunk
  return enum_for(:each_chunk) unless block_given?

  while (chunk = next_chunk)
    yield chunk
  end
end

#elapsed_msObject

Playback position last reported by the codec, in milliseconds.



99
100
101
# File 'lib/rockbox_ffi/decoder.rb', line 99

def elapsed_ms
  Lib.rb_decoder_elapsed_ms(@ptr)
end

#finishedObject

[done, code]: done is true once the track has ended; code is the exit status (0 = clean end, negative = codec error), valid only when done.



106
107
108
109
110
111
# File 'lib/rockbox_ffi/decoder.rb', line 106

def finished
  out_code = "\0" * Fiddle::SIZEOF_INT       # int32_t* out-param
  r = Lib.rb_decoder_finished(@ptr, out_code)
  done = r == true || r == 1
  [done, out_code.unpack1("l")]
end

#metadataObject

-- metadata --------------------------------------------------------- Tags and stream properties (same shape as Metadata.read). Returns a Hash with symbol keys. Raises on failure.



55
56
57
58
59
60
# File 'lib/rockbox_ffi/decoder.rb', line 55

def 
  s = RockboxFFI.take_string(Lib.(@ptr))
  raise "rb_decoder_metadata_json returned NULL" if s.nil?

  JSON.parse(s, symbolize_names: true)
end

#next_chunkObject

-- decoding --------------------------------------------------------- Next buffer of decoded PCM as [samples, sample_rate], where samples is an Array of interleaved stereo int16 Integers. Returns nil at end of track (or after a codec error — see #finished).



66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
# File 'lib/rockbox_ffi/decoder.rb', line 66

def next_chunk
  out_len = "\0" * Fiddle::SIZEOF_VOIDP      # size_t* out-param (i16 count)
  out_rate = "\0" * Fiddle::SIZEOF_INT       # uint32_t* out-param (Hz)
  ptr = Lib.rb_decoder_next_chunk(@ptr, out_len, out_rate)

  produced = out_len.unpack1(Fiddle::SIZEOF_VOIDP == 8 ? "Q" : "L")
  return nil if ptr.null? || produced.zero?

  rate = out_rate.unpack1("L")
  begin
    ptr.size = produced * 2
    [ptr[0, produced * 2].unpack("s<*"), rate]
  ensure
    Lib.rb_buffer_free(ptr, produced)
  end
end

#seek_ms(ms) ⇒ Object

Request a seek to ms milliseconds.



94
95
96
# File 'lib/rockbox_ffi/decoder.rb', line 94

def seek_ms(ms)
  Lib.rb_decoder_seek_ms(@ptr, Integer(ms))
end