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.
- data/.gitignore +20 -0
- data/.rspec +3 -0
- data/.rvmrc +1 -0
- data/.travis.yml +13 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +181 -0
- data/Rakefile +6 -0
- data/docs/id3v2.4.0-structure.txt +731 -0
- data/docs/lame-3.99.5.h +1323 -0
- data/lame.gemspec +28 -0
- data/lib/lame.rb +31 -0
- data/lib/lame/buffer.rb +21 -0
- data/lib/lame/configuration.rb +228 -0
- data/lib/lame/decoder.rb +38 -0
- data/lib/lame/decoding/decoded_frame.rb +25 -0
- data/lib/lame/decoding/id3_tag_parser.rb +53 -0
- data/lib/lame/decoding/mp3_data_header_parser.rb +64 -0
- data/lib/lame/decoding/mpeg_audio_frame_finder.rb +46 -0
- data/lib/lame/decoding/mpeg_audio_frame_matcher.rb +98 -0
- data/lib/lame/decoding/single_frame_decoder.rb +51 -0
- data/lib/lame/decoding/stream_decoder.rb +37 -0
- data/lib/lame/delegation.rb +68 -0
- data/lib/lame/encoder.rb +73 -0
- data/lib/lame/encoding/encode_short_buffer.rb +26 -0
- data/lib/lame/encoding/flusher.rb +22 -0
- data/lib/lame/encoding/id3.rb +46 -0
- data/lib/lame/encoding/vbr_info.rb +22 -0
- data/lib/lame/error.rb +13 -0
- data/lib/lame/ffi.rb +20 -0
- data/lib/lame/ffi/decode_flags.rb +19 -0
- data/lib/lame/ffi/enums.rb +75 -0
- data/lib/lame/ffi/functions.rb +272 -0
- data/lib/lame/ffi/global_flags.rb +20 -0
- data/lib/lame/ffi/mp3_data.rb +37 -0
- data/lib/lame/ffi/version.rb +17 -0
- data/lib/lame/version.rb +3 -0
- data/spec/buffer_spec.rb +26 -0
- data/spec/configuration_spec.rb +391 -0
- data/spec/decoder_spec.rb +120 -0
- data/spec/decoding/decoded_frame_spec.rb +44 -0
- data/spec/decoding/id3_tag_parser_spec.rb +54 -0
- data/spec/decoding/mp3_data_header_parser_spec.rb +95 -0
- data/spec/decoding/mpeg_audio_frame_finder_spec.rb +50 -0
- data/spec/decoding/mpeg_audio_frame_matcher_spec.rb +179 -0
- data/spec/decoding/single_frame_decoder_spec.rb +104 -0
- data/spec/decoding/stream_decoder_spec.rb +43 -0
- data/spec/delegation_spec.rb +146 -0
- data/spec/encoder_spec.rb +279 -0
- data/spec/encoding/encode_short_buffer_spec.rb +72 -0
- data/spec/encoding/flusher_spec.rb +47 -0
- data/spec/encoding/id3_spec.rb +106 -0
- data/spec/encoding/vbr_info_spec.rb +47 -0
- data/spec/ffi/decode_flags_spec.rb +16 -0
- data/spec/ffi/encoding_spec.rb +223 -0
- data/spec/ffi/global_flags_spec.rb +542 -0
- data/spec/ffi/id3tag_spec.rb +135 -0
- data/spec/ffi/mp3_data_spec.rb +26 -0
- data/spec/files/dies-irae.wav +0 -0
- data/spec/integration/decoding_spec.rb +179 -0
- data/spec/integration/encoding_spec.rb +126 -0
- data/spec/integration/id3_tags_spec.rb +96 -0
- data/spec/spec_helper.rb +175 -0
- metadata +254 -0
@@ -0,0 +1,46 @@
|
|
1
|
+
module LAME
|
2
|
+
module Decoding
|
3
|
+
class MPEGAudioFrameFinder
|
4
|
+
|
5
|
+
MPEG_HEADER_SIZE = 4
|
6
|
+
|
7
|
+
def initialize(stream)
|
8
|
+
@stream = stream
|
9
|
+
end
|
10
|
+
|
11
|
+
def find!
|
12
|
+
begin
|
13
|
+
@data = @stream.read(MPEG_HEADER_SIZE)
|
14
|
+
if MPEGAudioFrameMatcher.new(@data).match?
|
15
|
+
seek_back!
|
16
|
+
return
|
17
|
+
end
|
18
|
+
seek!
|
19
|
+
end until end_of_stream?
|
20
|
+
|
21
|
+
raise MPEGAudioFrameNotFoundError
|
22
|
+
end
|
23
|
+
|
24
|
+
private
|
25
|
+
|
26
|
+
def offset
|
27
|
+
@stream.pos
|
28
|
+
end
|
29
|
+
|
30
|
+
# Move to next 4 bytes.
|
31
|
+
def seek!
|
32
|
+
@stream.seek(offset - (MPEG_HEADER_SIZE-1))
|
33
|
+
end
|
34
|
+
|
35
|
+
# Move back to frame position.
|
36
|
+
def seek_back!
|
37
|
+
@stream.seek(offset - (MPEG_HEADER_SIZE))
|
38
|
+
end
|
39
|
+
|
40
|
+
def end_of_stream?
|
41
|
+
!@data || (@data.size != MPEG_HEADER_SIZE)
|
42
|
+
end
|
43
|
+
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
@@ -0,0 +1,98 @@
|
|
1
|
+
module LAME
|
2
|
+
module Decoding
|
3
|
+
|
4
|
+
# Specification is here:
|
5
|
+
# http://www.codeproject.com/Articles/8295/MPEG-Audio-Frame-Header
|
6
|
+
#
|
7
|
+
# Also see `is_syncword_mp123` in lame-3.99.5/frontend/get_audio.c
|
8
|
+
#
|
9
|
+
# Get ready for some bit-matching magic:
|
10
|
+
class MPEGAudioFrameMatcher
|
11
|
+
|
12
|
+
attr_reader :bytes
|
13
|
+
|
14
|
+
CHANNEL_MODES = [:stereo, :joint_stereo, :dual_channel, :mono]
|
15
|
+
|
16
|
+
def initialize(bytes)
|
17
|
+
@bytes = bytes.unpack("C*")
|
18
|
+
end
|
19
|
+
|
20
|
+
def match?
|
21
|
+
leading_bits? &&
|
22
|
+
mpeg? &&
|
23
|
+
layer? &&
|
24
|
+
bitrate? &&
|
25
|
+
sample_frequency? &&
|
26
|
+
valid_mpeg1_layer2? &&
|
27
|
+
emphasis?
|
28
|
+
end
|
29
|
+
|
30
|
+
private
|
31
|
+
|
32
|
+
# First eleven bits (all 1)
|
33
|
+
def leading_bits?
|
34
|
+
(bytes[0] & 0b11111111) == 0b11111111 &&
|
35
|
+
(bytes[1] & 0b11100000) == 0b11100000
|
36
|
+
end
|
37
|
+
|
38
|
+
# 4th and 5th bit in second byte (01 is reserved)
|
39
|
+
def mpeg?
|
40
|
+
(bytes[1] & 0b11000) != 0b01000
|
41
|
+
end
|
42
|
+
|
43
|
+
# 6th and 7th bit in second byte (00 is reserved)
|
44
|
+
def layer?
|
45
|
+
(bytes[1] & 0b110) != 0b000
|
46
|
+
end
|
47
|
+
|
48
|
+
# first 4 bits in third byte (all 1's is reserved)
|
49
|
+
def bitrate?
|
50
|
+
(bytes[2] & 0b11110000) != 0b11110000
|
51
|
+
end
|
52
|
+
|
53
|
+
# 5th and 6th bit in third byte (all 1's is reserved)
|
54
|
+
def sample_frequency?
|
55
|
+
(bytes[2] & 0b1100) != 0b1100
|
56
|
+
end
|
57
|
+
|
58
|
+
# http://www.codeproject.com/Articles/8295/MPEG-Audio-Frame-Header#Combinations
|
59
|
+
# Only certain combination are valid for MPEG1 Layer II:
|
60
|
+
def valid_mpeg1_layer2?
|
61
|
+
return true if !mpeg1?
|
62
|
+
return true if !layer2?
|
63
|
+
|
64
|
+
bitrate_index = (bytes[2] >> 4)
|
65
|
+
|
66
|
+
case bitrate_index
|
67
|
+
when 1,2,3,5
|
68
|
+
channel_mode == :mono
|
69
|
+
when 11,12,13,14
|
70
|
+
[:stereo, :joint_stereo, :dual_channel].include?(channel_mode)
|
71
|
+
else
|
72
|
+
true
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
# 7th and 8th bit in fourth byte (10 is reserved)
|
77
|
+
def emphasis?
|
78
|
+
(bytes[3] & 0b11) != 0b10
|
79
|
+
end
|
80
|
+
|
81
|
+
# MPEG1 = 11
|
82
|
+
def mpeg1?
|
83
|
+
(bytes[1] & 0b11000) == 0b11000
|
84
|
+
end
|
85
|
+
|
86
|
+
# Layer II = 10
|
87
|
+
def layer2?
|
88
|
+
(bytes[1] & 0b110) == 0b100
|
89
|
+
end
|
90
|
+
|
91
|
+
# 1st and 2nd bit of fourth byte
|
92
|
+
def channel_mode
|
93
|
+
@channel_mode ||= CHANNEL_MODES[bytes[3] >> 6]
|
94
|
+
end
|
95
|
+
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
module LAME
|
2
|
+
module Decoding
|
3
|
+
class SingleFrameDecoder
|
4
|
+
|
5
|
+
def initialize(decode_flags, mp3_data)
|
6
|
+
@decode_flags = decode_flags
|
7
|
+
@mp3_data = mp3_data
|
8
|
+
end
|
9
|
+
|
10
|
+
def decode(data)
|
11
|
+
flush_buffer do |decoded_frame|
|
12
|
+
yield decoded_frame
|
13
|
+
end
|
14
|
+
decode_data(data) do |decoded_frame|
|
15
|
+
yield decoded_frame
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
private
|
20
|
+
|
21
|
+
def flush_buffer(&block)
|
22
|
+
in_buffer = LAME::Buffer.create_empty(:uchar, 0)
|
23
|
+
decode_buffer(in_buffer, &block)
|
24
|
+
end
|
25
|
+
|
26
|
+
def decode_data(data, &block)
|
27
|
+
in_buffer = LAME::Buffer.create_uchar(data)
|
28
|
+
decode_buffer(in_buffer, &block)
|
29
|
+
end
|
30
|
+
|
31
|
+
def decode_buffer(in_buffer, &block)
|
32
|
+
out_left, out_right = output_buffers
|
33
|
+
result = LAME.hip_decode1_headers(@decode_flags, in_buffer, in_buffer.size, out_left, out_right, @mp3_data)
|
34
|
+
|
35
|
+
if result > 0
|
36
|
+
yield DecodedFrame.from_short_buffers(out_left, out_right)
|
37
|
+
flush_buffer(&block)
|
38
|
+
end
|
39
|
+
|
40
|
+
raise DecodingError if result < 0
|
41
|
+
end
|
42
|
+
|
43
|
+
def output_buffers
|
44
|
+
out_left = LAME::Buffer.create_empty(:short, 1152)
|
45
|
+
out_right = LAME::Buffer.create_empty(:short, 1152)
|
46
|
+
[out_left, out_right]
|
47
|
+
end
|
48
|
+
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
module LAME
|
2
|
+
module Decoding
|
3
|
+
class StreamDecoder
|
4
|
+
|
5
|
+
# arbitrary, taken from get_audio.c#lame_decode_fromfile
|
6
|
+
DECODE_SIZE = 1024
|
7
|
+
|
8
|
+
def initialize(decode_flags, mp3_data, stream)
|
9
|
+
@stream = stream
|
10
|
+
@single_frame_decoder = SingleFrameDecoder.new(decode_flags, mp3_data)
|
11
|
+
end
|
12
|
+
|
13
|
+
def each_decoded_frame(&block)
|
14
|
+
begin
|
15
|
+
@data = @stream.read(DECODE_SIZE)
|
16
|
+
|
17
|
+
decode &block
|
18
|
+
end until end_of_stream?
|
19
|
+
end
|
20
|
+
|
21
|
+
private
|
22
|
+
|
23
|
+
def decode
|
24
|
+
return if end_of_stream?
|
25
|
+
|
26
|
+
@single_frame_decoder.decode(@data) do |decoded_frame|
|
27
|
+
yield decoded_frame
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def end_of_stream?
|
32
|
+
!@data
|
33
|
+
end
|
34
|
+
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,68 @@
|
|
1
|
+
module LAME
|
2
|
+
module Delegation
|
3
|
+
|
4
|
+
def delegate_to_lame(*delegations)
|
5
|
+
delegations.each do |flag|
|
6
|
+
define_setter_delegator(flag, flag)
|
7
|
+
define_getter_delegator(flag, flag)
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
def delegate_alias_to_lame(delegations)
|
12
|
+
delegations.each_pair do |from, to|
|
13
|
+
define_setter_delegator(from, to)
|
14
|
+
define_getter_delegator(from, to)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
def delegate_id3_to_lame(*delegations)
|
19
|
+
delegations.each do |flag|
|
20
|
+
define_setter_delegator(flag, flag, "id3tag")
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
private
|
25
|
+
|
26
|
+
def define_setter_delegator(from, to, preset = "lame")
|
27
|
+
define_method(:"#{from}=") do |value|
|
28
|
+
value = TypeConvertor.convert(value)
|
29
|
+
LAME.send(:"#{preset}_set_#{to}", global_flags, value)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def define_getter_delegator(from, to, preset = "lame")
|
34
|
+
define_method(:"#{from}") do
|
35
|
+
LAME.send(:"#{preset}_get_#{to}", global_flags)
|
36
|
+
end
|
37
|
+
define_method(:"#{from}?") do
|
38
|
+
return_value = send(:"#{from}")
|
39
|
+
TypeConvertor.convert_return(return_value)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
class TypeConvertor
|
44
|
+
def self.convert(value)
|
45
|
+
case value
|
46
|
+
when true
|
47
|
+
1
|
48
|
+
when false
|
49
|
+
0
|
50
|
+
when String
|
51
|
+
::FFI::MemoryPointer.from_string(value)
|
52
|
+
else
|
53
|
+
value
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
def self.convert_return(value)
|
58
|
+
case value
|
59
|
+
when 0
|
60
|
+
false
|
61
|
+
else
|
62
|
+
!!value
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
end
|
68
|
+
end
|
data/lib/lame/encoder.rb
ADDED
@@ -0,0 +1,73 @@
|
|
1
|
+
module LAME
|
2
|
+
class Encoder
|
3
|
+
|
4
|
+
attr_reader :global_flags
|
5
|
+
|
6
|
+
def initialize
|
7
|
+
@global_flags = FFI::GlobalFlags.new
|
8
|
+
end
|
9
|
+
|
10
|
+
def configure
|
11
|
+
yield configuration
|
12
|
+
apply_configuration
|
13
|
+
end
|
14
|
+
|
15
|
+
def encode_short(left, right)
|
16
|
+
apply_configuration
|
17
|
+
|
18
|
+
each_frame(left, right) do |left_frame, right_frame|
|
19
|
+
mp3_data = Encoding::EncodeShortBuffer.new(configuration).encode_frame(left_frame, right_frame)
|
20
|
+
yield mp3_data
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def flush(&block)
|
25
|
+
mp3_data = Encoding::Flusher.new(configuration).flush
|
26
|
+
yield_or_return(mp3_data, &block)
|
27
|
+
end
|
28
|
+
|
29
|
+
def vbr_frame(&block)
|
30
|
+
mp3_data = Encoding::VBRInfo.new(configuration).frame
|
31
|
+
yield_or_return(mp3_data, &block)
|
32
|
+
end
|
33
|
+
|
34
|
+
def id3v1(&block)
|
35
|
+
mp3_data = Encoding::Id3.new(configuration).v1
|
36
|
+
yield_or_return(mp3_data, &block)
|
37
|
+
end
|
38
|
+
|
39
|
+
def id3v2(&block)
|
40
|
+
mp3_data = Encoding::Id3.new(configuration).v2
|
41
|
+
yield_or_return(mp3_data, &block)
|
42
|
+
end
|
43
|
+
|
44
|
+
def configuration
|
45
|
+
@configuration ||= Configuration.new(global_flags)
|
46
|
+
end
|
47
|
+
|
48
|
+
def framesize
|
49
|
+
configuration.framesize
|
50
|
+
end
|
51
|
+
|
52
|
+
private
|
53
|
+
|
54
|
+
def apply_configuration
|
55
|
+
configuration.apply! unless configuration.applied?
|
56
|
+
end
|
57
|
+
|
58
|
+
def each_frame(left, right)
|
59
|
+
left.zip(right).each_slice(framesize) do |slice|
|
60
|
+
yield slice.transpose
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
def yield_or_return(value)
|
65
|
+
if block_given?
|
66
|
+
yield value
|
67
|
+
else
|
68
|
+
value
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
end
|
73
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
module LAME
|
2
|
+
module Encoding
|
3
|
+
class EncodeShortBuffer
|
4
|
+
extend Forwardable
|
5
|
+
|
6
|
+
def_delegators :@configuration, :global_flags, :framesize, :output_buffer_size
|
7
|
+
|
8
|
+
def initialize(configuration)
|
9
|
+
@configuration = configuration
|
10
|
+
end
|
11
|
+
|
12
|
+
def encode_frame(left, right)
|
13
|
+
left_buffer = Buffer.create(:short, left)
|
14
|
+
right_buffer = Buffer.create(:short, right)
|
15
|
+
output = Buffer.create_empty(:uchar, output_buffer_size)
|
16
|
+
|
17
|
+
mp3_size = LAME.lame_encode_buffer(global_flags,
|
18
|
+
left_buffer, right_buffer, left.size,
|
19
|
+
output, output_buffer_size)
|
20
|
+
|
21
|
+
output.get_bytes(0, mp3_size)
|
22
|
+
end
|
23
|
+
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
module LAME
|
2
|
+
module Encoding
|
3
|
+
class Flusher
|
4
|
+
extend Forwardable
|
5
|
+
|
6
|
+
def_delegators :@configuration, :global_flags, :framesize, :output_buffer_size
|
7
|
+
|
8
|
+
def initialize(configuration)
|
9
|
+
@configuration = configuration
|
10
|
+
end
|
11
|
+
|
12
|
+
def flush
|
13
|
+
output = Buffer.create_empty(:uchar, output_buffer_size)
|
14
|
+
|
15
|
+
mp3_size = LAME.lame_encode_flush(global_flags, output, output_buffer_size)
|
16
|
+
|
17
|
+
output.get_bytes(0, mp3_size)
|
18
|
+
end
|
19
|
+
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
module LAME
|
2
|
+
module Encoding
|
3
|
+
class Id3
|
4
|
+
extend Forwardable
|
5
|
+
|
6
|
+
def_delegators :@configuration, :global_flags, :framesize, :output_buffer_size
|
7
|
+
|
8
|
+
def initialize(configuration)
|
9
|
+
@configuration = configuration
|
10
|
+
end
|
11
|
+
|
12
|
+
def v1
|
13
|
+
tag_size = v1_tag_size
|
14
|
+
output = output_buffer(tag_size)
|
15
|
+
LAME.lame_get_id3v1_tag(global_flags, output, tag_size)
|
16
|
+
output.get_bytes(0, tag_size)
|
17
|
+
end
|
18
|
+
|
19
|
+
def v2
|
20
|
+
tag_size = v2_tag_size
|
21
|
+
output = output_buffer(tag_size)
|
22
|
+
LAME.lame_get_id3v2_tag(global_flags, output, tag_size)
|
23
|
+
output.get_bytes(0, tag_size)
|
24
|
+
end
|
25
|
+
|
26
|
+
private
|
27
|
+
|
28
|
+
def output_buffer(size)
|
29
|
+
Buffer.create_empty(:uchar, size)
|
30
|
+
end
|
31
|
+
|
32
|
+
# LAME returns the required buffer size if the input buffer is too small.
|
33
|
+
def v1_tag_size
|
34
|
+
small_buffer = output_buffer(0)
|
35
|
+
LAME.lame_get_id3v1_tag(global_flags, small_buffer, 0)
|
36
|
+
end
|
37
|
+
|
38
|
+
# LAME returns the required buffer size if the input buffer is too small.
|
39
|
+
def v2_tag_size
|
40
|
+
small_buffer = output_buffer(0)
|
41
|
+
LAME.lame_get_id3v2_tag(global_flags, small_buffer, 0)
|
42
|
+
end
|
43
|
+
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|