icanhasaudio 0.1.1 → 0.1.2
Sign up to get free protection for your applications and to get access to all the features.
- data/History.txt +6 -0
- data/Manifest.txt +24 -14
- data/README.txt +2 -0
- data/Rakefile +15 -8
- data/ext/icanhasaudio/audio_mpeg_decoder.c +220 -0
- data/ext/icanhasaudio/audio_mpeg_decoder.h +8 -0
- data/ext/icanhasaudio/audio_mpeg_decoder_mp3data.c +96 -0
- data/ext/icanhasaudio/audio_mpeg_decoder_mp3data.h +8 -0
- data/ext/{mpeg_encoder.c → icanhasaudio/audio_mpeg_encoder.c} +94 -102
- data/ext/icanhasaudio/audio_mpeg_encoder.h +8 -0
- data/ext/{rb_ogg.c → icanhasaudio/audio_ogg_decoder.c} +18 -9
- data/ext/icanhasaudio/audio_ogg_decoder.h +8 -0
- data/ext/{extconf.rb → icanhasaudio/extconf.rb} +2 -2
- data/ext/{get_audio.c → icanhasaudio/get_audio.c} +1 -6
- data/ext/{get_audio.h → icanhasaudio/get_audio.h} +2 -0
- data/ext/icanhasaudio/native.c +9 -0
- data/ext/icanhasaudio/native.h +21 -0
- data/ext/{rb_wav.c → icanhasaudio/rb_wav.c} +1 -2
- data/ext/{rb_wav.h → icanhasaudio/rb_wav.h} +1 -2
- data/lib/icanhasaudio.rb +4 -0
- data/lib/icanhasaudio/mpeg.rb +1 -24
- data/lib/icanhasaudio/mpeg/decoder.rb +95 -0
- data/lib/icanhasaudio/ogg.rb +1 -21
- data/lib/icanhasaudio/ogg/decoder.rb +31 -0
- data/lib/icanhasaudio/version.rb +8 -0
- data/lib/icanhasaudio/wav.rb +1 -0
- data/lib/icanhasaudio/wav/file.rb +62 -0
- data/test/assets/icha.mp3 +0 -0
- data/test/helper.rb +15 -0
- data/test/mpeg/test_decoder.rb +20 -0
- data/test/test_mpeg_encoder.rb +2 -4
- metadata +31 -18
- data/ext/decoder.c +0 -95
- data/ext/decoder.h +0 -3
- data/ext/icanhasaudio.c +0 -183
- data/ext/icanhasaudio.h +0 -8
- data/ext/mpeg_encoder.h +0 -10
- data/ext/syncword.c +0 -45
- data/ext/syncword.h +0 -3
@@ -1,9 +1,10 @@
|
|
1
|
-
#include <
|
2
|
-
#include <ogg/ogg.h>
|
3
|
-
#include <vorbis/vorbisfile.h>
|
4
|
-
#include <rb_wav.h>
|
1
|
+
#include <native.h>
|
5
2
|
|
6
|
-
size_t rb_ogg_read(void *ptr,
|
3
|
+
static size_t rb_ogg_read( void *ptr,
|
4
|
+
size_t size,
|
5
|
+
size_t nmemb,
|
6
|
+
void *datasource)
|
7
|
+
{
|
7
8
|
VALUE file = (VALUE)datasource;
|
8
9
|
VALUE str;
|
9
10
|
size_t length;
|
@@ -29,24 +30,24 @@ int rb_ogg_seek(void *datasource, ogg_int64_t offset, int whence) {
|
|
29
30
|
return -1;
|
30
31
|
}
|
31
32
|
|
32
|
-
int rb_ogg_close(void *datasource) {
|
33
|
+
static int rb_ogg_close(void *datasource) {
|
33
34
|
VALUE file = (VALUE)datasource;
|
34
35
|
rb_funcall(file, rb_intern("close"), 0);
|
35
36
|
return 0;
|
36
37
|
}
|
37
38
|
|
38
|
-
long rb_ogg_tell(void *datasource) {
|
39
|
+
static long rb_ogg_tell(void *datasource) {
|
39
40
|
VALUE file = (VALUE)datasource;
|
40
41
|
return NUM2LONG(rb_funcall(file, rb_intern("tell"), 0));
|
41
42
|
}
|
42
43
|
|
43
44
|
/*
|
44
45
|
* call-seq:
|
45
|
-
*
|
46
|
+
* native_decode(input_io, output_io)
|
46
47
|
*
|
47
48
|
* Decode the input IO and write it to the output IO.
|
48
49
|
*/
|
49
|
-
VALUE
|
50
|
+
VALUE native_decode(VALUE self, VALUE infile, VALUE outf) {
|
50
51
|
OggVorbis_File vf;
|
51
52
|
ov_callbacks callbacks;
|
52
53
|
int bs = 0;
|
@@ -137,3 +138,11 @@ VALUE method_ogg_decode(VALUE self, VALUE infile, VALUE outf) {
|
|
137
138
|
|
138
139
|
return Qnil;
|
139
140
|
}
|
141
|
+
void init_audio_ogg_decoder()
|
142
|
+
{
|
143
|
+
VALUE rb_mAudio = rb_define_module("Audio");
|
144
|
+
VALUE rb_mOgg = rb_define_module_under(rb_mAudio, "OGG");
|
145
|
+
VALUE klass = rb_define_class_under(rb_mOgg, "Decoder", rb_cObject);
|
146
|
+
|
147
|
+
rb_define_private_method(klass, "native_decode", native_decode, 2);
|
148
|
+
}
|
@@ -0,0 +1,21 @@
|
|
1
|
+
#ifndef ICANHASAUDIO_H
|
2
|
+
#define ICANHASAUDIO_H
|
3
|
+
|
4
|
+
#include <ruby.h>
|
5
|
+
#include <rubyio.h>
|
6
|
+
#include <lame/lame.h>
|
7
|
+
#include <dlfcn.h>
|
8
|
+
#include <assert.h>
|
9
|
+
#include <ogg/ogg.h>
|
10
|
+
#include <vorbis/vorbisfile.h>
|
11
|
+
|
12
|
+
#include <get_audio.h>
|
13
|
+
#include <rb_wav.h>
|
14
|
+
#include <audio_mpeg_decoder.h>
|
15
|
+
#include <audio_mpeg_decoder_mp3data.h>
|
16
|
+
#include <audio_mpeg_encoder.h>
|
17
|
+
#include <audio_ogg_decoder.h>
|
18
|
+
|
19
|
+
void Init_native();
|
20
|
+
|
21
|
+
#endif
|
data/lib/icanhasaudio.rb
ADDED
data/lib/icanhasaudio/mpeg.rb
CHANGED
@@ -1,25 +1,2 @@
|
|
1
1
|
require 'icanhasaudio/mpeg/encoder'
|
2
|
-
|
3
|
-
class Audio::MPEG::Decoder
|
4
|
-
attr_reader :stereo, :samplerate, :bitrate, :mode, :mode_ext, :framesize
|
5
|
-
|
6
|
-
# Number of bits, 8 or 16
|
7
|
-
attr_accessor :bits
|
8
|
-
|
9
|
-
def initialize
|
10
|
-
@bits = 16
|
11
|
-
@raw = nil
|
12
|
-
yield self if block_given?
|
13
|
-
end
|
14
|
-
|
15
|
-
private
|
16
|
-
def attempt_rewind(outf)
|
17
|
-
begin
|
18
|
-
outf.seek(0, IO::SEEK_SET)
|
19
|
-
true
|
20
|
-
rescue
|
21
|
-
false
|
22
|
-
end
|
23
|
-
end
|
24
|
-
end
|
25
|
-
|
2
|
+
require 'icanhasaudio/mpeg/decoder'
|
@@ -0,0 +1,95 @@
|
|
1
|
+
module Audio
|
2
|
+
module MPEG
|
3
|
+
class Decoder
|
4
|
+
# Number of bits, 8 or 16
|
5
|
+
attr_accessor :bits
|
6
|
+
attr_reader :mp3data
|
7
|
+
|
8
|
+
def initialize
|
9
|
+
@bits = 16
|
10
|
+
@raw = false
|
11
|
+
@mp3data = MP3Data.new
|
12
|
+
yield self if block_given?
|
13
|
+
end
|
14
|
+
|
15
|
+
def decode input, output
|
16
|
+
buf = skip_id3_header(input)
|
17
|
+
|
18
|
+
decode_headers_for(buf)
|
19
|
+
while !mp3data.header_parsed?
|
20
|
+
decode_headers_for(input.read(100))
|
21
|
+
end
|
22
|
+
mp3data.nsamp = MP3Data::MAX_U_32_NUM unless mp3data.total_frames > 0
|
23
|
+
wav = WAV::File.new(output)
|
24
|
+
wav.write_header(0x7FFFFFFF, 0, num_channels, in_samplerate) if !@raw
|
25
|
+
native_decode(input, wav)
|
26
|
+
if !@raw && attempt_rewind(wav)
|
27
|
+
wav.write_header(@wavsize + 44, 0, num_channels, in_samplerate)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
private
|
32
|
+
ID3 = [73, 68, 51, 3].pack('C*')
|
33
|
+
AID = [65, 105, 68, 1].pack('C*')
|
34
|
+
|
35
|
+
def skip_id3_header input
|
36
|
+
header = input.read(4)
|
37
|
+
if header == ID3
|
38
|
+
puts "asdfadsf"
|
39
|
+
id3_len = input.read(6).unpack('C*')[2..-1].map { |chr|
|
40
|
+
chr & 127
|
41
|
+
}.inject(0) { |total,chr|
|
42
|
+
(total + chr) << 7
|
43
|
+
} >> 7
|
44
|
+
input.read(id3_len) # skip the ID3 tag
|
45
|
+
header = input.read(4)
|
46
|
+
end
|
47
|
+
raise "Found AiD header" if header == AID
|
48
|
+
|
49
|
+
while !syncword_mp123?(header)
|
50
|
+
header = header.slice(1..-1) + input.getc
|
51
|
+
end
|
52
|
+
header
|
53
|
+
end
|
54
|
+
|
55
|
+
def syncword_mp123? header
|
56
|
+
alb2 = [0, 7, 7, 7, 0, 7, 0, 0, 0, 0, 0, 8, 8, 8, 8, 8]
|
57
|
+
p_h = header.unpack('C*')
|
58
|
+
return false if p_h[0] & 0xFF != 0xFF # first 8 bits must be '1'
|
59
|
+
return false if p_h[1] & 0xE0 != 0xE0 # next 3 bits are also
|
60
|
+
return false if p_h[1] & 0x18 == 0x08 # no MPEG-1, -2, or -2.5
|
61
|
+
return false if p_h[1] & 0x06 == 0x00 # no Layer I, II and III
|
62
|
+
return false if p_h[1] & 0x06 == 0x03 * 2 # layer1 not supported
|
63
|
+
return false if p_h[1] & 0x06 == 0x02 * 2 # layer1 not supported
|
64
|
+
return false unless p_h[1] & 0x06 == 0x01 * 2 # incompatible layer
|
65
|
+
return false if p_h[2] & 0xF0 == 0xF0 # bad bitrate
|
66
|
+
return false if p_h[2] & 0x0C == 0x0C # no sample frequency
|
67
|
+
|
68
|
+
if( (p_h[1] & 0x18 == 0x18) && (p_h[1] & 0x06 == 0x04) )
|
69
|
+
if(abl2[p_h[2] >> 4] & (1 << (p_h[3] >> 6)) != 0)
|
70
|
+
return false
|
71
|
+
end
|
72
|
+
end
|
73
|
+
return false if p_h[3] & 3 == 2
|
74
|
+
true
|
75
|
+
end
|
76
|
+
|
77
|
+
def attempt_rewind outf
|
78
|
+
begin
|
79
|
+
outf.seek(0, IO::SEEK_SET)
|
80
|
+
true
|
81
|
+
rescue
|
82
|
+
false
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
def determine_samples_for infile
|
87
|
+
length = File.stat(infile.path).size
|
88
|
+
total_seconds = length * 8.0 / (1000.0 * mp3data.bitrate)
|
89
|
+
self.num_samples = (total_seconds * in_samplerate).to_i
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
|
data/lib/icanhasaudio/ogg.rb
CHANGED
@@ -1,21 +1 @@
|
|
1
|
-
|
2
|
-
# Set to true for no WAV header
|
3
|
-
attr_accessor :raw
|
4
|
-
|
5
|
-
# Number of bits, 8 or 16
|
6
|
-
attr_accessor :bits
|
7
|
-
|
8
|
-
# Endianness
|
9
|
-
attr_accessor :endian
|
10
|
-
|
11
|
-
# Signedness
|
12
|
-
attr_accessor :sign
|
13
|
-
|
14
|
-
def initialize
|
15
|
-
@raw = false
|
16
|
-
@bits = 16
|
17
|
-
@endian = 0
|
18
|
-
@sign = 1
|
19
|
-
yield self if block_given?
|
20
|
-
end
|
21
|
-
end
|
1
|
+
require 'icanhasaudio/ogg/decoder'
|
@@ -0,0 +1,31 @@
|
|
1
|
+
module Audio
|
2
|
+
module OGG
|
3
|
+
class Decoder
|
4
|
+
# Set to true for no WAV header
|
5
|
+
attr_accessor :raw
|
6
|
+
|
7
|
+
# Number of bits, 8 or 16
|
8
|
+
attr_accessor :bits
|
9
|
+
|
10
|
+
# Endianness
|
11
|
+
attr_accessor :endian
|
12
|
+
|
13
|
+
# Signedness
|
14
|
+
attr_accessor :sign
|
15
|
+
|
16
|
+
def initialize
|
17
|
+
@raw = false
|
18
|
+
@bits = 16
|
19
|
+
@endian = 0
|
20
|
+
@sign = 1
|
21
|
+
yield self if block_given?
|
22
|
+
end
|
23
|
+
|
24
|
+
####
|
25
|
+
# Decode +input+ and write to +output+
|
26
|
+
def decode input, output
|
27
|
+
native_decode(input, output)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1 @@
|
|
1
|
+
require 'icanhasaudio/wav/file'
|
@@ -0,0 +1,62 @@
|
|
1
|
+
module Audio
|
2
|
+
module WAV
|
3
|
+
class File
|
4
|
+
attr_accessor :bits
|
5
|
+
def initialize io_or_path, mode = 'wb'
|
6
|
+
@io = io_or_path.is_a?(IO) ? io_or_path : File.open(io_or_path, mode)
|
7
|
+
@bits = 16
|
8
|
+
if block_given?
|
9
|
+
yield self
|
10
|
+
close
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
def write_header size, known_length, channels, samplerate
|
15
|
+
if known_length != 0 && known_length * bits / 8 * channels < size
|
16
|
+
size = known_length * bits / 8 * channels + 44
|
17
|
+
end
|
18
|
+
|
19
|
+
bytespersec = channels * samplerate * bits / 8
|
20
|
+
align = channels * bits / 8
|
21
|
+
|
22
|
+
header = [
|
23
|
+
'RIFF',
|
24
|
+
u32(size - 8),
|
25
|
+
'WAVE',
|
26
|
+
'fmt ',
|
27
|
+
u32(16),
|
28
|
+
u16(1),
|
29
|
+
u16(channels),
|
30
|
+
u32(samplerate),
|
31
|
+
u32(bytespersec),
|
32
|
+
u16(align),
|
33
|
+
u16(bits),
|
34
|
+
'data',
|
35
|
+
u32(size - 44),
|
36
|
+
].join
|
37
|
+
@io.write header
|
38
|
+
end
|
39
|
+
|
40
|
+
def write *args
|
41
|
+
@io.write(*args)
|
42
|
+
end
|
43
|
+
|
44
|
+
def close *args
|
45
|
+
@io.close(*args)
|
46
|
+
end
|
47
|
+
|
48
|
+
def seek *args
|
49
|
+
@io.seek(*args)
|
50
|
+
end
|
51
|
+
|
52
|
+
private
|
53
|
+
def u32 num
|
54
|
+
[0, 8, 16, 24].map { |x| (num >> x) & 0xFF }.pack('C4')
|
55
|
+
end
|
56
|
+
|
57
|
+
def u16 num
|
58
|
+
[0, 8].map { |x| (num >> x) & 0xFF }.pack('C2')
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
Binary file
|
data/test/helper.rb
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
require 'md5'
|
2
|
+
require 'test/unit'
|
3
|
+
require 'icanhasaudio'
|
4
|
+
require 'tempfile'
|
5
|
+
|
6
|
+
%w(../lib ../ext).each do |path|
|
7
|
+
$LOAD_PATH.unshift(File.expand_path(File.join(File.dirname(__FILE__), path)))
|
8
|
+
end
|
9
|
+
|
10
|
+
module ICANHASAUDIO
|
11
|
+
class TestCase < Test::Unit::TestCase
|
12
|
+
MP3_FILE = File.dirname(__FILE__) + "/assets/icha.mp3"
|
13
|
+
undef :default_test
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
require File.expand_path(File.join(File.dirname(__FILE__), '..', 'helper'))
|
2
|
+
|
3
|
+
module Audio
|
4
|
+
module MPEG
|
5
|
+
class TestDecoder < ICANHASAUDIO::TestCase
|
6
|
+
def setup
|
7
|
+
@decoder = MPEG::Decoder.new
|
8
|
+
end
|
9
|
+
|
10
|
+
def test_decode
|
11
|
+
out = "#{Dir::tmpdir}/out.wav"
|
12
|
+
File.open(out, 'wb+') { |outfile|
|
13
|
+
@decoder.decode(File.open(MP3_FILE, 'rb'), outfile)
|
14
|
+
}
|
15
|
+
digest = Digest::MD5.hexdigest(File.read(out))
|
16
|
+
#assert_equal '9a55bcdda77ec7c20f32031632927403', digest
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|