e0b8aa320d9c510f86dc0a330f3d9bc733e639dc
3 ### A simple program for doing blind A/B audio comparisons
5 ### (c) 2010 Mark Wooding
8 ###----- Licensing notice ---------------------------------------------------
10 ### This program is free software; you can redistribute it and/or modify
11 ### it under the terms of the GNU General Public License as published by
12 ### the Free Software Foundation; either version 2 of the License, or
13 ### (at your option) any later version.
15 ### This program is distributed in the hope that it will be useful,
16 ### but WITHOUT ANY WARRANTY; without even the implied warranty of
17 ### MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 ### GNU General Public License for more details.
20 ### You should have received a copy of the GNU General Public License
21 ### along with this program; if not, write to the Free Software Foundation,
22 ### Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
24 ###----- Usage --------------------------------------------------------------
26 ### The command line syntax is:
28 ### ab-chop INPUT CAPS OUTPUT PIPELINE...
30 ### This means that we should read INPUT, decode it (using a GStreamer
31 ### `decodebin', so it should be able to handle most things you care to throw
32 ### at it), and then re-encode it according to each PIPELINE in turn, decode
33 ### /that/ again, and stash the resulting raw PCM data. When we've finished,
34 ### we line up the PCM data streams side-by-side, chop them into chunks, and
35 ### then stitch chunks from randomly chosen streams together to make a new
36 ### PCM stream. Finally, we encode that mixed-up stream as FLAC, and write
37 ### it to OUTPUT. It also writes a file OUTPUT.sequence which is a list of
38 ### numbers indicating which pipeline each chunk of the original came from.
40 ### The motivation is that we want to test encoder quality. So you take a
41 ### reference source (as good as you can find), and use that as your INPUT.
42 ### You then write GStreamer pipeline fragments for the encoders you want to
43 ### compare; say `identity' if you want the unmodified original reference to
46 ### The only tricky bit is the CAPS, which is a GStreamer capabilities string
47 ### describing the raw PCM format to use as an intermediate representation.
48 ### (This is far too low-level and cumbersome for real use, but it's OK for
49 ### now.) You need to say something like
51 ### audio/x-raw-int,width=16,rate=44100,channels=2,depth=16,
52 ### endianness=1234,signed=true
54 ### for standard CD audio.
56 ###--------------------------------------------------------------------------
57 ### External dependencies.
59 ## Standard Python libraries.
68 ## GObject and GStreamer.
72 ###--------------------------------------------------------------------------
73 ### GStreamer utilities.
75 def link_on_demand(src
, sink
, sinkpad
= None, cap
= None):
77 Link SINK to SRC when a pad appears.
79 More precisely, when SRC reports that a pad with media type matching the
80 `fnmatch' pattern CAP has appeared, link the pad of SINK named SINKPAD (or
81 some sensible pad by default).
83 def _link(src
, srcpad
):
84 if cap
is None or FN
.fnmatchcase(srcpad
.get_caps()[0].get_name(), cap
):
85 src
.link_pads(srcpad
.get_name(), sink
, sinkpad
)
86 src
.connect('pad-added', _link
)
88 def make_element(factory
, name
= None, **props
):
90 Return an element made by FACTORY with properties specified by PROPS.
92 elt
= GS
.element_factory_make(factory
, name
)
93 elt
.set_properties(**props
)
96 def dump_pipeline(pipe
, indent
= 0):
99 for e
in pipe
.iterate_sources():
107 print '%s%s %s' %
(' '*indent
, type(e
).__name__
, e
.get_name())
109 c
= p
.get_negotiated_caps()
111 print '%s Pad %s %s (%s)' % \
114 peer
and ('<-> %s.%s' %
(peer
.get_parent().get_name(),
117 c
and c
.to_string() or 'no-negotiated-caps')
119 q
.append(peer
.get_parent())
120 if isinstance(e
, GS
.Bin
):
121 dump_pipeline(e
, indent
+ 1)
123 def run_pipe(pipe
, what
):
125 Run a GStreamer pipeline PIPE until it finishes.
129 bus
.add_signal_watch()
130 def _bus_message(bus
, msg
):
131 if msg
.type == GS
.MESSAGE_ERROR
:
132 SYS
.stderr
.write('error from pipeline: %s\n' % msg
)
134 elif msg
.type == GS
.MESSAGE_STATE_CHANGED
and \
135 msg
.src
== pipe
and \
136 msg
.structure
['new-state'] == GS
.STATE_PAUSED
:
138 elif msg
.type == GS
.MESSAGE_EOS
:
140 bus
.connect('message', _bus_message
)
142 pipe
.set_state(GS
.STATE_PLAYING
)
144 GS
.DEBUG_BIN_TO_DOT_FILE(pipe
, 3, what
)
145 pipe
.set_state(GS
.STATE_NULL
)
147 ###--------------------------------------------------------------------------
150 ## Read the command line arguments.
152 caps
= GS
.caps_from_string(SYS
.argv
[2])
155 ## We want a temporary place to keep things. This provokes a warning, but
156 ## `mkdir' is atomic and sane so it's not a worry.
161 ## First step: produce raw PCM files from the original source and the
162 ## requested encoders.
165 for i
in SYS
.argv
[4:]:
166 temp
= OS
.path
.join(tmp
, '%d.raw' % q
)
169 origin
= make_element('filesrc', location
= input)
170 decode_1
= make_element('decodebin')
171 convert_1
= make_element('audioconvert')
172 encode
= GS
.parse_bin_from_description(i
, True)
173 decode_2
= make_element('decodebin')
174 convert_2
= make_element('audioconvert')
175 target
= make_element('filesink', location
= temp
)
176 pipe
.add(origin
, decode_1
, convert_1
, encode
,
177 decode_2
, convert_2
, target
)
178 origin
.link(decode_1
)
179 link_on_demand(decode_1
, convert_1
)
180 ##convert_1.link(encode, GS.caps_from_string('audio/x-raw-float, channels=2'))
181 convert_1
.link(encode
)
182 encode
.link(decode_2
)
183 link_on_demand(decode_2
, convert_2
)
184 convert_2
.link(target
, caps
)
186 run_pipe(pipe
, 'input-%d' % q
)
191 lens
= [OS
.stat(i
).st_size
for i
in temps
]
192 blocks
= (max(*lens
) + step
- 1)//step
196 for i
in xrange(blocks
):
207 ff
= [open(i
, 'rb') for i
in temps
]
208 mix
= OS
.path
.join(tmp
, 'mix.raw')
209 out
= open(mix
, 'wb')
223 f
= open(output
+ '.sequence', 'w')
224 f
.write(', '.join([str(i
) for i
in seq
]) + '\n')
228 origin
= make_element('filesrc', location
= mix
)
229 convert
= make_element('audioconvert')
230 encode
= make_element('flacenc', quality
= 8)
231 target
= make_element('filesink', location
= output
)
232 pipe
.add(origin
, convert
, encode
, target
)
233 origin
.link(convert
, caps
)
234 GS
.element_link_many(convert
, encode
, target
)
236 run_pipe(pipe
, 'output')