lame 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
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