lame 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (64) hide show
  1. data/.gitignore +20 -0
  2. data/.rspec +3 -0
  3. data/.rvmrc +1 -0
  4. data/.travis.yml +13 -0
  5. data/Gemfile +4 -0
  6. data/LICENSE.txt +22 -0
  7. data/README.md +181 -0
  8. data/Rakefile +6 -0
  9. data/docs/id3v2.4.0-structure.txt +731 -0
  10. data/docs/lame-3.99.5.h +1323 -0
  11. data/lame.gemspec +28 -0
  12. data/lib/lame.rb +31 -0
  13. data/lib/lame/buffer.rb +21 -0
  14. data/lib/lame/configuration.rb +228 -0
  15. data/lib/lame/decoder.rb +38 -0
  16. data/lib/lame/decoding/decoded_frame.rb +25 -0
  17. data/lib/lame/decoding/id3_tag_parser.rb +53 -0
  18. data/lib/lame/decoding/mp3_data_header_parser.rb +64 -0
  19. data/lib/lame/decoding/mpeg_audio_frame_finder.rb +46 -0
  20. data/lib/lame/decoding/mpeg_audio_frame_matcher.rb +98 -0
  21. data/lib/lame/decoding/single_frame_decoder.rb +51 -0
  22. data/lib/lame/decoding/stream_decoder.rb +37 -0
  23. data/lib/lame/delegation.rb +68 -0
  24. data/lib/lame/encoder.rb +73 -0
  25. data/lib/lame/encoding/encode_short_buffer.rb +26 -0
  26. data/lib/lame/encoding/flusher.rb +22 -0
  27. data/lib/lame/encoding/id3.rb +46 -0
  28. data/lib/lame/encoding/vbr_info.rb +22 -0
  29. data/lib/lame/error.rb +13 -0
  30. data/lib/lame/ffi.rb +20 -0
  31. data/lib/lame/ffi/decode_flags.rb +19 -0
  32. data/lib/lame/ffi/enums.rb +75 -0
  33. data/lib/lame/ffi/functions.rb +272 -0
  34. data/lib/lame/ffi/global_flags.rb +20 -0
  35. data/lib/lame/ffi/mp3_data.rb +37 -0
  36. data/lib/lame/ffi/version.rb +17 -0
  37. data/lib/lame/version.rb +3 -0
  38. data/spec/buffer_spec.rb +26 -0
  39. data/spec/configuration_spec.rb +391 -0
  40. data/spec/decoder_spec.rb +120 -0
  41. data/spec/decoding/decoded_frame_spec.rb +44 -0
  42. data/spec/decoding/id3_tag_parser_spec.rb +54 -0
  43. data/spec/decoding/mp3_data_header_parser_spec.rb +95 -0
  44. data/spec/decoding/mpeg_audio_frame_finder_spec.rb +50 -0
  45. data/spec/decoding/mpeg_audio_frame_matcher_spec.rb +179 -0
  46. data/spec/decoding/single_frame_decoder_spec.rb +104 -0
  47. data/spec/decoding/stream_decoder_spec.rb +43 -0
  48. data/spec/delegation_spec.rb +146 -0
  49. data/spec/encoder_spec.rb +279 -0
  50. data/spec/encoding/encode_short_buffer_spec.rb +72 -0
  51. data/spec/encoding/flusher_spec.rb +47 -0
  52. data/spec/encoding/id3_spec.rb +106 -0
  53. data/spec/encoding/vbr_info_spec.rb +47 -0
  54. data/spec/ffi/decode_flags_spec.rb +16 -0
  55. data/spec/ffi/encoding_spec.rb +223 -0
  56. data/spec/ffi/global_flags_spec.rb +542 -0
  57. data/spec/ffi/id3tag_spec.rb +135 -0
  58. data/spec/ffi/mp3_data_spec.rb +26 -0
  59. data/spec/files/dies-irae.wav +0 -0
  60. data/spec/integration/decoding_spec.rb +179 -0
  61. data/spec/integration/encoding_spec.rb +126 -0
  62. data/spec/integration/id3_tags_spec.rb +96 -0
  63. data/spec/spec_helper.rb +175 -0
  64. metadata +254 -0
@@ -0,0 +1,135 @@
1
+ require 'spec_helper'
2
+
3
+ module LAME
4
+ describe "FFI calls to id3tag setters" do
5
+
6
+ before do
7
+ @flags_pointer = LAME.lame_init
8
+ end
9
+
10
+ after do
11
+ LAME.lame_close(@flags_pointer)
12
+ end
13
+
14
+ it "has all the genres" do
15
+ genres = {}
16
+
17
+ genre_collector_callback = ::FFI::Function.new(:void, [:int, :string, :pointer]) do |id, name, _|
18
+ genres[id] = name
19
+ end
20
+
21
+ LAME.id3tag_genre_list(genre_collector_callback, nil)
22
+
23
+ genres.should have(148).items
24
+ genres[0].should eql "Blues"
25
+ genres[147].should eql "SynthPop"
26
+ end
27
+
28
+ it "initializes" do
29
+ LAME.id3tag_init(@flags_pointer).should eql nil
30
+ end
31
+
32
+ it "adds v2" do
33
+ LAME.id3tag_add_v2(@flags_pointer).should eql nil
34
+ end
35
+
36
+ it "set v1 only" do
37
+ LAME.id3tag_v1_only(@flags_pointer).should eql nil
38
+ end
39
+
40
+ it "set v2 only" do
41
+ LAME.id3tag_v2_only(@flags_pointer).should eql nil
42
+ end
43
+
44
+ it "pads with spaces" do
45
+ LAME.id3tag_space_v1(@flags_pointer).should eql nil
46
+ end
47
+
48
+ it "pads v2 with 128 extra bytes" do
49
+ LAME.id3tag_pad_v2(@flags_pointer).should eql nil
50
+ end
51
+
52
+ it "pads v2 with extra bytes" do
53
+ LAME.id3tag_set_pad(@flags_pointer, 256).should eql nil
54
+ end
55
+
56
+ it "sets the title" do
57
+ LAME.id3tag_set_title(@flags_pointer, pointer_from_string("foo")).should eql nil
58
+ end
59
+
60
+ it "sets the artist" do
61
+ LAME.id3tag_set_artist(@flags_pointer, pointer_from_string("foo")).should eql nil
62
+ end
63
+
64
+ it "sets the album" do
65
+ LAME.id3tag_set_album(@flags_pointer, pointer_from_string("foo")).should eql nil
66
+ end
67
+
68
+ it "sets the year" do
69
+ LAME.id3tag_set_year(@flags_pointer, pointer_from_string("foo")).should eql nil
70
+ end
71
+
72
+ it "sets the comment" do
73
+ LAME.id3tag_set_comment(@flags_pointer, pointer_from_string("foo")).should eql nil
74
+ end
75
+
76
+ it "sets the track" do
77
+ LAME.id3tag_set_track(@flags_pointer, pointer_from_string("1")).should eql 0
78
+ end
79
+
80
+ it "ignores out of range track numbers for id3" do
81
+ LAME.id3tag_set_track(@flags_pointer, pointer_from_string("256")).should eql -1
82
+ end
83
+
84
+ it "sets the genre" do
85
+ LAME.id3tag_set_genre(@flags_pointer, pointer_from_string("Rock")).should eql 0
86
+ end
87
+
88
+ # There is a fixed set of allowed fields (see id3tag.c)
89
+ # LAME 3.99.4 fixed some bugs in setting field values, this could crash for certain tags.
90
+ it "sets the fieldvalue" do
91
+ LAME.id3tag_set_fieldvalue(@flags_pointer, pointer_from_string("TIT2=foofoo")).should eql 0
92
+ end
93
+
94
+ # it "sets the fieldvalue (utf16)" do
95
+ # LAME.id3tag_set_fieldvalue_utf16(@flags_pointer, "LINK=foofoo".encode("UTF-16")).should eql 0
96
+ # end
97
+
98
+ it "sets album art" do
99
+ buffer_size = 1024
100
+
101
+ # fake JPG image
102
+ buffer = ::FFI::MemoryPointer.new(:char, buffer_size)
103
+ buffer.put_char(0, 0xff)
104
+ buffer.put_char(1, 0xd8)
105
+
106
+ LAME.id3tag_set_albumart(@flags_pointer, buffer, buffer_size).should eql 0
107
+ end
108
+
109
+ it "creates id3v1 tag" do
110
+ buffer_size = 1024
111
+ buffer = ::FFI::MemoryPointer.new(:uchar, buffer_size)
112
+ LAME.id3tag_set_title(@flags_pointer, pointer_from_string("foo"))
113
+ LAME.id3tag_set_album(@flags_pointer, pointer_from_string("bar"))
114
+ LAME.lame_get_id3v1_tag(@flags_pointer, buffer, buffer_size).should eql 128
115
+ end
116
+
117
+ it "creates id3v2 tag" do
118
+ buffer_size = 1024
119
+ buffer = ::FFI::MemoryPointer.new(:uchar, buffer_size)
120
+ LAME.id3tag_add_v2(@flags_pointer)
121
+ LAME.id3tag_set_title(@flags_pointer, pointer_from_string("foo"))
122
+ LAME.id3tag_set_album(@flags_pointer, pointer_from_string("bar"))
123
+ LAME.lame_get_id3v2_tag(@flags_pointer, buffer, buffer_size).should eql 38
124
+ end
125
+
126
+ it "sets id3tag automatic" do
127
+ LAME.should have_flag(:write_id3tag_automatic).with_value(1).for(@flags_pointer)
128
+ end
129
+
130
+ it "gets id3tag automatic" do
131
+ LAME.should be_able_to_set(:write_id3tag_automatic).to(0).for(@flags_pointer).and_return(nil)
132
+ end
133
+
134
+ end
135
+ end
@@ -0,0 +1,26 @@
1
+ require 'spec_helper'
2
+
3
+ module LAME
4
+ module FFI
5
+ describe MP3Data do
6
+
7
+ subject(:mp3_data) { MP3Data.new }
8
+
9
+ it "is stereo for two channels" do
10
+ mp3_data[:stereo] = 2
11
+ mp3_data.channel_mode.should eql :stereo
12
+ end
13
+
14
+ it "is mono for one channel" do
15
+ mp3_data[:stereo] = 1
16
+ mp3_data.channel_mode.should eql :mono
17
+ end
18
+
19
+ it "has a sample rate" do
20
+ mp3_data[:samplerate] = 44100
21
+ mp3_data.sample_rate.should eql 44100
22
+ end
23
+
24
+ end
25
+ end
26
+ end
Binary file
@@ -0,0 +1,179 @@
1
+ require 'spec_helper'
2
+ require 'wavefile'
3
+
4
+ describe "Decoding", :slow => true do
5
+
6
+ let(:mp3_file_path) { File.expand_path(File.join(File.dirname(__FILE__), '../files/dies-irae-cli-id3v2.mp3')) }
7
+ let(:mp3_file) { File.open(mp3_file_path, "r") }
8
+
9
+ it "decodes an MP3 file by api" do
10
+ decoder = LAME::Decoder.new(mp3_file)
11
+
12
+ format = WaveFile::Format.new(decoder.channel_mode, :pcm_16, decoder.sample_rate)
13
+
14
+ WaveFile::Writer.new("output1.wav", format) do |writer|
15
+
16
+ decoder.each_decoded_frame do |decoded_frame|
17
+ buffer = WaveFile::Buffer.new(decoded_frame.samples, format)
18
+
19
+ writer.write(buffer)
20
+ end
21
+
22
+ end
23
+ end
24
+
25
+ it "decodes an MP3 file" do
26
+ # id3
27
+ id3_length = id3_length(mp3_file)
28
+ puts "ID3 length: #{id3_length} bytes"
29
+ mp3_file.read(id3_length)
30
+
31
+ # aid TODO (ignored for now)
32
+
33
+ # find MP3 sync frame
34
+
35
+ mp3_offset = mpeg_audio_offset(mp3_file, id3_length)
36
+ puts "offset: #{mp3_offset}"
37
+
38
+ @decode_flags = LAME::FFI::DecodeFlags.new
39
+ @mp3_data = LAME::FFI::MP3Data.new
40
+
41
+ # Decode until we have parsed an MP3 header
42
+ mp3_file.seek(mp3_offset)
43
+ begin
44
+ find_header(mp3_file)
45
+ end until @mp3_data.header_parsed?
46
+
47
+ # Results:
48
+ if @mp3_data.header_parsed?
49
+ puts "stereo: #{@mp3_data[:stereo]}"
50
+ puts "samplerate: #{@mp3_data[:samplerate]}"
51
+ puts "bitrate: #{@mp3_data[:bitrate]}"
52
+ puts "mode: #{@mp3_data[:mode]}"
53
+ puts "mod_ext: #{@mp3_data[:mod_ext]}"
54
+ puts "framesize: #{@mp3_data[:framesize]}"
55
+ puts "nsamp: #{@mp3_data[:nsamp]}"
56
+ puts "totalframes: #{@mp3_data[:totalframes]}"
57
+ puts "framenum: #{@mp3_data[:framenum]}"
58
+ end
59
+
60
+ # read mp3_data (number of frames etc)
61
+ format = WaveFile::Format.new(:stereo, :pcm_16, 44100)
62
+ WaveFile::Writer.new("output2.wav", format) do |writer|
63
+
64
+ # See get_audio.c:2082 #lame_decode_fromfile
65
+ #
66
+ # Read until we have decode some MP3 data:
67
+ @result = 0
68
+ begin
69
+ @result = decode(mp3_file)
70
+
71
+ if @result && @result.any?
72
+ left = @result[0].read_array_of_short(@result[0].size/2)
73
+ right = @result[1].read_array_of_short(@result[1].size/2)
74
+ buffer = WaveFile::Buffer.new(left.zip(right), format)
75
+
76
+ writer.write(buffer)
77
+ end
78
+ end until !@result
79
+ end
80
+ end
81
+
82
+ def find_header(mp3_file)
83
+ size = 100 # arbitrary, taken from get_audio.c#lame_decode_initfile
84
+ in_data = mp3_file.read(size)
85
+
86
+ in_buffer = LAME::Buffer.create_uchar(in_data)
87
+ out_left = LAME::Buffer.create_empty(:short, 0)
88
+ out_right = LAME::Buffer.create_empty(:short, 0)
89
+
90
+ enc_delay = ::FFI::MemoryPointer.new(:int, 1)
91
+ enc_padding = ::FFI::MemoryPointer.new(:int, 1)
92
+
93
+ result = LAME.hip_decode1_headersB(@decode_flags, in_buffer, size, out_left, out_right, @mp3_data, enc_delay, enc_padding)
94
+
95
+ if @mp3_data.header_parsed?
96
+ puts "header parsed @ #{mp3_file.pos}"
97
+ puts "enc_delay: #{enc_delay.read_array_of_int(1)}"
98
+ puts "enc_padding: #{enc_padding.read_array_of_int(1)}"
99
+ end
100
+ end
101
+
102
+ def decode(mp3_file)
103
+ size = 1024 # arbitrary, taken from get_audio.c#lame_decode_fromfile
104
+ out_left = LAME::Buffer.create_empty(:short, 1152)
105
+ out_right = LAME::Buffer.create_empty(:short, 1152)
106
+
107
+ in_buffer = LAME::Buffer.create_empty(:uchar, 0)
108
+
109
+ # see if we have anything left in the internal decode buffer
110
+ result = LAME.hip_decode1_headers(@decode_flags, in_buffer, 0, out_left, out_right, @mp3_data)
111
+
112
+ case result
113
+ when -1
114
+ raise "decoding error (a)"
115
+ when 0
116
+ # need more data
117
+ else
118
+ return [out_left, out_right]
119
+ end
120
+
121
+ in_data = mp3_file.read(size)
122
+ if !in_data
123
+ return nil
124
+ end
125
+
126
+ in_buffer = LAME::Buffer.create_uchar(in_data)
127
+
128
+ # else read more data and try again
129
+ result = LAME.hip_decode1_headers(@decode_flags, in_buffer, size, out_left, out_right, @mp3_data)
130
+
131
+ case result
132
+ when -1
133
+ raise "decoding error (b)"
134
+ when 0
135
+ # need more data
136
+ else
137
+ return [out_left, out_right]
138
+ end
139
+
140
+ []
141
+ end
142
+
143
+ def mpeg_audio_offset(mp3_file, offset = 0)
144
+ mp3_file.seek(offset)
145
+
146
+ window_size = 4
147
+ begin
148
+ in_data = mp3_file.read(window_size)
149
+
150
+ if LAME::Decoding::MPEGAudioFrameMatcher.new(in_data).match?
151
+ puts "match offset @ #{offset} : #{in_data.bytes.to_a[0].to_s(2)} #{in_data.bytes.to_a[1].to_s(2)} #{in_data.bytes.to_a[2].to_s(2)} #{in_data.bytes.to_a[3].to_s(2)}"
152
+ return offset
153
+ end
154
+
155
+ offset += 1
156
+ mp3_file.seek(offset)
157
+ end while in_data.length == window_size
158
+ end
159
+
160
+ def id3_length(file)
161
+ header = file.read(10)
162
+ if id3?(header)
163
+ size = header[-4..-1]
164
+
165
+ b0 = size[0].ord
166
+ b1 = size[1].ord
167
+ b2 = size[2].ord
168
+ b3 = size[3].ord
169
+
170
+ # rubify this sometime:
171
+ (((((b0 << 7) + b1) << 7) + b2) << 7) + b3
172
+ end
173
+ end
174
+
175
+ def id3?(header)
176
+ header.start_with?("ID3")
177
+ end
178
+
179
+ end
@@ -0,0 +1,126 @@
1
+ require 'spec_helper'
2
+ require 'wavefile'
3
+ # require 'digest'
4
+
5
+ describe "Encoding", :slow => true do
6
+
7
+ let(:wav_path) { File.expand_path(File.join(File.dirname(__FILE__), '../files/dies-irae.wav')) }
8
+ let(:mp3_path_raw) { File.expand_path(File.join(File.dirname(__FILE__), '../files/dies-irae-raw.mp3')) }
9
+ let(:mp3_path_api) { File.expand_path(File.join(File.dirname(__FILE__), '../files/dies-irae-api.mp3')) }
10
+
11
+ let(:wav_reader) { WaveFile::Reader.new(wav_path) }
12
+
13
+ # This test serves as an example how to use the LAME API
14
+ it "encodes a wav file" do
15
+
16
+ # setup
17
+ flags_pointer = LAME.lame_init
18
+ LAME.lame_init_params(flags_pointer)
19
+
20
+ # LAME.id3tag_init(flags_pointer) # needed?
21
+ # LAME.id3tag_set_title(flags_pointer, "foo")
22
+
23
+ # number of samples to read
24
+ framesize = LAME.lame_get_framesize(flags_pointer)
25
+
26
+ # input buffers
27
+ left_buffer = ::FFI::MemoryPointer.new(:short, framesize)
28
+ right_buffer = ::FFI::MemoryPointer.new(:short, framesize)
29
+
30
+ # output buffer
31
+ buffer_size = (128*1024)+16384
32
+ buffer = ::FFI::MemoryPointer.new(:uchar, buffer_size)
33
+
34
+ File.open(mp3_path_raw, "wb") do |file|
35
+ wav_reader.each_buffer(framesize) do |read_buffer|
36
+
37
+ # read samples (ranges from -32k to +32k)
38
+ read_buffer.samples.each.with_index do |(left, right), index|
39
+ byte_offset = index * 2
40
+ left_buffer.put_short(byte_offset, left)
41
+ right_buffer.put_short(byte_offset, right)
42
+ end
43
+
44
+ input_buffer_size = read_buffer.samples.size
45
+
46
+ # encode to mp3 frame
47
+ size = LAME.lame_encode_buffer(
48
+ flags_pointer,
49
+ left_buffer, right_buffer, input_buffer_size,
50
+ buffer, buffer_size
51
+ )
52
+
53
+ # write to file
54
+ file.write buffer.get_bytes(0, size)
55
+ end
56
+
57
+ # flush final frame
58
+ size = LAME.lame_encode_flush(flags_pointer, buffer, buffer_size)
59
+ file.write buffer.get_bytes(0, size)
60
+
61
+ # write "lametag" frame with extra info
62
+ size = LAME.lame_get_lametag_frame(flags_pointer, buffer, buffer_size)
63
+ file.seek(0)
64
+ file.write buffer.get_bytes(0, size)
65
+ end
66
+
67
+ # close
68
+ LAME.lame_close(flags_pointer)
69
+
70
+ # TODO: Need a better way to test output..
71
+ # Digest::MD5.hexdigest(File.read(mp3_path_raw)).should eql "84a1ce7994bb4a54fc13fb5381ebac40"
72
+ end
73
+
74
+ it "encodes a file by api" do
75
+
76
+ encoder = LAME::Encoder.new
77
+
78
+ encoder.configure do |config|
79
+ config.id3.write_automatic = false
80
+ config.id3.v2 = true
81
+ config.id3.title = "Dies Irae"
82
+ config.id3.genre = "Classical"
83
+
84
+ #config.bitrate = 192
85
+
86
+ # config.preset = :V0 # doesn't work?
87
+ config.vbr.mode = :vbr_default
88
+ config.vbr.q = 0
89
+ end
90
+
91
+ File.open(mp3_path_api, "wb") do |file|
92
+
93
+ id3v2_size = 0
94
+ encoder.id3v2 do |tag|
95
+ file.write tag
96
+ id3v2_size = tag.size
97
+ end
98
+
99
+ wav_reader.each_buffer(encoder.framesize) do |read_buffer|
100
+ left = read_buffer.samples.map { |s| s[0] }
101
+ right = read_buffer.samples.map { |s| s[1] }
102
+
103
+ encoder.encode_short(left, right) do |mp3|
104
+ file.write mp3
105
+ end
106
+ end
107
+ encoder.flush do |flush_frame|
108
+ file.write(flush_frame)
109
+ end
110
+
111
+ encoder.id3v1 do |tag|
112
+ file.write tag
113
+ end
114
+
115
+ encoder.vbr_frame do |vbr_frame|
116
+ file.seek(id3v2_size)
117
+ file.write(vbr_frame)
118
+ end
119
+
120
+ end
121
+
122
+ # TODO: Need a better way to test output..
123
+ # Digest::MD5.hexdigest(File.read(mp3_path_api)).should eql "d1cd92c106e7aac4f5291fd141a19e10"
124
+ end
125
+
126
+ end