test_gst_transcode_configurator.js
Go to the documentation of this file.
1 /* vim: set sw=2 : miv*/
2 /*
3  *=BEGIN SONGBIRD GPL
4  *
5  * This file is part of the Songbird web player.
6  *
7  * Copyright(c) 2005-2010 POTI, Inc.
8  * http://www.songbirdnest.com
9  *
10  * This file may be licensed under the terms of of the
11  * GNU General Public License Version 2 (the ``GPL'').
12  *
13  * Software distributed under the License is distributed
14  * on an ``AS IS'' basis, WITHOUT WARRANTY OF ANY KIND, either
15  * express or implied. See the GPL for the specific language
16  * governing rights and limitations.
17  *
18  * You should have received a copy of the GPL along with this
19  * program. If not, go to http://www.gnu.org/licenses/gpl.html
20  * or write to the Free Software Foundation, Inc.,
21  * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
22  *
23  *=END SONGBIRD GPL
24  */
25 
32 Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
33 Components.utils.import("resource://app/jsmodules/ArrayConverter.jsm");
34 
39 function CopyProperties(aPropertyBag) {
40  var newProps = Cc["@songbirdnest.com/moz/xpcom/sbpropertybag;1"]
41  .createInstance(Ci.nsIWritablePropertyBag2);
42  if (aPropertyBag) {
43  // stuff the property bag into a SIP to make sure instanceof works correctly
44  var sip = Cc["@mozilla.org/supports-interface-pointer;1"]
45  .createInstance(Ci.nsISupportsInterfacePointer);
46  sip.data = aPropertyBag;
47  if (sip.data instanceof Ci.nsIPropertyBag) {
48  for each (var prop in ArrayConverter.JSEnum(sip.data.enumerator)) {
49  newProps.setProperty(prop.name, prop.value);
50  }
51  }
52  else {
53  // not a property bag; assume this is a simple JS object
54  for (var prop in aPropertyBag) {
55  newProps.setProperty(prop, aPropertyBag[prop]);
56  }
57  }
58  }
59  return newProps;
60 }
61 
65 function F(aNumerator, aDenominator) {
66  return {
67  numerator: aNumerator,
68  denominator: aDenominator,
69  QueryInterface: XPCOMUtils.generateQI([Ci.sbIDevCapFraction])
70  };
71 }
72 
78 function sbDevCapRange(aInput) {
79  var range = Cc["@songbirdnest.com/Songbird/Device/sbrange;1"]
80  .createInstance(Ci.sbIDevCapRange);
81  if (aInput === null) {
82  // possibly no size ranges, or something
83  return null;
84  }
85  if (aInput instanceof Array) {
86  for each (let val in aInput) {
87  range.AddValue(val);
88  }
89  }
90  else {
91  range.Initialize(aInput.min, aInput.max, aInput.step);
92  }
93  return range;
94 }
95 
96 /*
97  * The default input specifications
98  */
99 const K_DEFAULT_INPUT = {
100  inputUri: "x-transcode-test:default-input",
101  container: {
102  containerType: "container/default",
103  properties: CopyProperties(null)
104  },
105  videoStream: {
106  videoType: "video/default",
107  videoWidth: 640,
108  videoHeight: 480,
109  PAR: F(1, 1),
110  frameRate: F(30000, 1001),
111  bitRate: 500000,
112  getVideoPAR: function videoFormat_getVideoPAR(aNum, aDenom) {
113  aNum.value = this.PAR.numerator;
114  aDenom.value = this.PAR.denominator;
115  },
116  getVideoFrameRate: function videoFormat_getVideoFrameRate(aNum, aDenom) {
117  aNum.value = this.frameRate.numerator;
118  aDenom.value = this.frameRate.denominator;
119  },
120  properties: CopyProperties(null)
121  },
122  audioStream: {
123  audioType: "audio/default",
124  bitRate: 128000,
125  sampleRate: 44100,
126  channels: 2,
127  properties: CopyProperties(null)
128  },
129  QueryInterface: XPCOMUtils.generateQI([Ci.sbIMediaFormat])
130 };
131 
132 /*
133  * The default output specifications (device caps)
134  */
135 const K_DEFAULT_CAPS = {
136  type: "video",
137  containerType: "application/ogg",
138  video: {
139  type: "video/x-theora",
140  explicitSizes: [{width: 320, height: 240}],
141  widths: {min: 16, step: 16, max: 320},
142  heights: {min: 16, step: 16, max: 240},
143  PARs: [F(1, 1)],
144  frameRates: [F(15, 1), F(30000, 1001)],
145  bitRates: {min: 0, step:1, max: 4000000}
146  },
147  audio: {
148  type: "audio/x-vorbis",
149  bitRates: {min: 0, step: 1, max: 400000},
150  sampleRates: [44100],
151  channels: [1, 2]
152  }
153 };
154 
155 /*
156  * The default output values
157  */
159  muxer: "oggmux",
160  fileExtension: "ogg",
161  videoEncoder: "theoraenc",
162  audioEncoder: "vorbisenc",
163  videoFormat: {
164  width: 320,
165  height: 240,
166  PAR: F(1, 1),
167  frameRate: F(30000, 1001),
168  properties: {}
169  },
170  audioFormat: {
171  sampleRate: 44100,
172  channels: 2,
173  properties: {}
174  }
175 };
176 
177 function checkBitrate(aCaps, aBitrate, aMin, aMax)
178 {
179  assertTrue(aBitrate >= aCaps.video.bitRates.min / 1000);
180  assertTrue(aBitrate <= aCaps.video.bitRates.max / 1000);
181  assertTrue(aBitrate >= aMin, "Bitrate too low");
182  assertTrue(aBitrate <= aMax, "Bitrate too high");
183  return true;
184 }
185 
186 const K_TEST_CASES = [
187  { description: "default",
188  output: {
189  videoFormat: {
190  properties: {
191  bitrate: function(bitrate) {
192  return checkBitrate(this.caps, bitrate, 600, 700);
193  }
194  }
195  },
196  audioFormat: {
197  properties: {
198  "max-bitrate": 128000
199  }
200  }
201  }
202  },
203  { description: "scale down with empty areas",
204  input: {
205  videoStream: {
206  videoWidth: 1280,
207  videoHeight: 720
208  }
209  },
210  output: {
211  videoFormat: {
212  width: 320,
213  height: 192, // 320 / 16:9 = 180, rounded up to nearest multiple of 16
214  properties: {
215  bitrate: function(bitrate) {
216  return checkBitrate(this.caps, bitrate, 500, 600);
217  }
218  }
219  }
220  }
221  },
222  { description: "scale down with padding",
223  input: {
224  videoStream: {
225  videoWidth: 1280,
226  videoHeight: 720
227  }
228  },
229  caps: {
230  video: {
231  // We still use explicitSizes here (forcing 320x240)
232  widths: null,
233  heights: null
234  }
235  },
236  output: {
237  videoFormat: {
238  width: 320,
239  height: 240
240  }
241  }
242  },
243  { description: "scale up with empty areas",
244  input: {
245  videoStream: {
246  videoWidth: 16,
247  videoHeight: 16
248  }
249  },
250  caps: {
251  video: {
252  widths: {min: 320, max: 1024, step: 16},
253  heights: {min: 240, max: 1024, step: 16}
254  }
255  },
256  output: {
257  videoFormat: {
258  width: 320,
259  height: 320
260  }
261  }
262  },
263  { description: "scale up with padding",
264  input: {
265  videoStream: {
266  videoWidth: 16,
267  videoHeight: 16
268  }
269  },
270  caps: {
271  video: {
272  widths: null,
273  heights: null
274  }
275  },
276  output: {
277  videoFormat: {
278  width: 320,
279  height: 240
280  }
281  }
282  },
283  { description: "reduce fps",
284  input: {
285  videoStream: {
286  frameRate: F(24000, 1001)
287  }
288  },
289  caps: {
290  video: {
291  frameRates: [F(15, 1), F(60000, 1001)]
292  }
293  },
294  output: {
295  videoFormat: {
296  frameRate: F(15, 1)
297  }
298  }
299  },
300  { description: "increase fps",
301  input: {
302  videoStream: {
303  frameRate: F(24000, 1001)
304  }
305  },
306  caps: {
307  video: {
308  frameRates: [F(15, 1), F(30000, 1001)]
309  }
310  },
311  output: {
312  videoFormat: {
313  frameRate: F(30000, 1001)
314  }
315  }
316  },
317  { description: "no change",
318  input: {
319  videoStream: {
320  videoWidth: 800,
321  videoHeight: 600,
322  PAR: F(1, 1),
323  frameRate: F(24000, 1001)
324  }
325  },
326  caps: {
327  video: {
328  widths: {min: 640, step: 1, max: 1024},
329  heights: {min: 480, step: 1, max: 768},
330  PARs: [F(1, 1), F(2, 1), F(3, 2)],
331  frameRates: {min: F(0, 1), max: F(30000, 1001)},
332  bitRates: {min: 1, step: 1, max: 2000000}
333  }
334  },
335  output: {
336  videoFormat: {
337  width: 800,
338  height: 600,
339  PAR: F(1, 1),
340  frameRate: F(24000, 1001),
341  properties: {
342  bitrate: function(bitrate) {
343  // Max bitrate is 2000000; ensure we capped it to this for this
344  // high-res test.
345  return checkBitrate(this.caps, bitrate, 2000, 2000);
346  }
347  }
348  }
349  }
350  },
351  { description: "favour size over quality",
352  input: {
353  videoStream: {
354  videoWidth: 1920,
355  videoHeight: 816,
356  PAR: F(1, 1),
357  frameRate: F(24000, 1001)
358  }
359  },
360  caps: {
361  video: {
362  widths: {min: 1, max: 720, step: 1},
363  heights: {min: 1, max: 576, step: 1},
364  bitRates: {min: 0, max: 1536000, step: 1},
365  frameRates: {min: F(0, 1), max: F(30000, 1001)}
366  }
367  },
368  output: {
369  videoFormat: {
370  width: 720,
371  height: 306,
372  frameRate: F(24000, 1001)
373  }
374  }
375  }
376 ];
377 
384 function fromFractionRange(aFractions) {
385  return ArrayConverter.nsIArray((aFractions instanceof Array) ?
386  aFractions :
387  [aFractions.min, aFractions.max]);
388 }
389 
390 function runTest() {
391  // Set up a test library to use for the configurator.
392  var testlib = createLibrary("test_gst_configurator");
393 
394  for each (var testcase in K_TEST_CASES) {
395  log("Checking testcase [" + testcase.description + "]");
396  // Create a new configurator to test with.
397  var configurator =
398  Cc["@songbirdnest.com/Songbird/Mediacore/Transcode/Configurator/Device/GStreamer;1"]
399  .createInstance(Ci.sbIDeviceTranscodingConfigurator);
400  assertTrue(configurator, "failed to create configurator");
401 
402  // fix up the test case to inherit from the default
403  if (!testcase.hasOwnProperty("input")) {
404  testcase.input = {};
405  }
406  testcase.input.__proto__ = K_DEFAULT_INPUT;
407  if (!testcase.hasOwnProperty("caps")) {
408  testcase.caps = {};
409  }
410  testcase.caps.__proto__ = K_DEFAULT_CAPS;
411  if (!testcase.hasOwnProperty("output")) {
412  testcase.output = {};
413  }
414  testcase.output.__proto__ = K_DEFAULT_OUTPUT;
415  for each (let prop in ["container", "videoStream", "audioStream"]) {
416  if (testcase.input.hasOwnProperty(prop)) {
417  testcase.input[prop].__proto__ = K_DEFAULT_INPUT[prop];
418  }
419  }
420  for each (let prop in ["video", "audio"]) {
421  if (testcase.caps.hasOwnProperty(prop)) {
422  testcase.caps[prop].__proto__ = K_DEFAULT_CAPS[prop];
423  }
424  }
425  for each (let prop in ["videoFormat", "audioFormat"]) {
426  if (testcase.output.hasOwnProperty(prop)) {
427  testcase.output[prop].__proto__ = K_DEFAULT_OUTPUT[prop];
428  }
429  }
430 
431  // need to set an input URI so the error handling can report something;
432  // the value actually used here isn't important.
433  configurator.inputUri = newURI(testcase.input.inputUri);
434 
435  // the input format interface is simple enough to use the JS object directly
436  configurator.inputFormat = testcase.input;
437 
438  // the device caps is not so simple; we need to construct something more
439  // complex to appropriately fake all the interfaces
440  var videoCaps = Cc["@songbirdnest.com/Songbird/Device/sbdevcapvideostream;1"]
441  .createInstance(Ci.sbIDevCapVideoStream);
442  var videoSizes = [{width: x.width, height: x.height,
443  QueryInterface: XPCOMUtils.generateQI([Ci.sbIImageSize])}
444  for each (x in testcase.caps.video.explicitSizes)];
445  videoCaps.initialize(testcase.caps.video.type,
446  ArrayConverter.nsIArray(videoSizes),
447  sbDevCapRange(testcase.caps.video.widths),
448  sbDevCapRange(testcase.caps.video.heights),
449  fromFractionRange(testcase.caps.video.PARs),
450  !(testcase.caps.video.PARs instanceof Array),
451  fromFractionRange(testcase.caps.video.frameRates),
452  !(testcase.caps.video.frameRates instanceof Array),
453  sbDevCapRange(testcase.caps.video.bitRates));
454 
455  var audioCaps = Cc["@songbirdnest.com/Songbird/Device/sbdevcapaudiostream;1"]
456  .createInstance(Ci.sbIDevCapAudioStream);
457  audioCaps.initialize(testcase.caps.audio.type,
458  sbDevCapRange(testcase.caps.audio.bitRates),
459  sbDevCapRange(testcase.caps.audio.sampleRates),
460  sbDevCapRange(testcase.caps.audio.channels));
461 
462  var formatType = Cc["@songbirdnest.com/Songbird/Device/sbvideoformattype;1"]
463  .createInstance(Ci.sbIVideoFormatType);
464  formatType.initialize(testcase.caps.containerType,
465  videoCaps,
466  audioCaps);
467 
468  var caps = Cc["@songbirdnest.com/Songbird/Device/DeviceCapabilities;1"]
469  .createInstance(Ci.sbIDeviceCapabilities);
470  caps.init();
471  caps.setFunctionTypes([caps.FUNCTION_VIDEO_PLAYBACK], 1);
472  caps.addContentTypes(caps.FUNCTION_VIDEO_PLAYBACK, [caps.CONTENT_VIDEO], 1);
473  caps.addMimeTypes(caps.CONTENT_VIDEO, [testcase.caps.containerType], 1);
474  caps.AddFormatType(caps.CONTENT_VIDEO,
475  testcase.caps.containerType,
476  formatType);
477  caps.configureDone();
478 
479  // Test the configurator by setting a device on it
480  configurator.device = #1= {
481  capabilities: caps,
482  getPreference: function device_getPreference(aPrefName) {
483  switch (aPrefName) {
484  case "transcode.quality.video":
485  return testcase.hasOwnProperty("quality") ? testcase.quality : 0.5;
486  }
487  throw Components.results.NS_ERROR_NOT_IMPLEMENTED;
488  },
489  wrappedJSObject: #1#
490  };
491  configurator.determineOutputType();
492 
494  // actually run the test
496 
497  // configurate
498  configurator.configurate();
499 
500  // remove things from the device, to get rid of the closure that will end up
501  // leaking this whole test. However, we can't just ask the configurator to
502  // null out the device, because that's not supported.
503  for (let prop in configurator.device.wrappedJSObject) {
504  if (prop != "wrappedJSObject") {
505  delete configurator.device.wrappedJSObject[prop];
506  }
507  }
508  delete configurator.device.wrappedJSObject.wrappedJSObject;
509 
510  // check the muxer
511  if (testcase.output.muxer != null) {
512  assertTrue(configurator.useMuxer,
513  "expected configurator to use a muxer");
514  assertEqual(testcase.output.muxer,
515  configurator.muxer,
516  "expected muxer is not configured muxer");
517  }
518  else {
519  assertFalse(configurator.useMuxer,
520  "expected configurator to not use a muxer");
521  }
522 
523  // check the file extension
524  assertEqual(testcase.output.fileExtension,
525  configurator.fileExtension,
526  "expected file extension is not configured file extension");
527 
528  // check the video encoder
529  if (testcase.output.videoEncoder != null) {
530  assertEqual(testcase.output.videoEncoder,
531  configurator.videoEncoder,
532  "expected video encoder is not configured video encoder");
533  assertTrue(configurator.useVideoEncoder,
534  "expected configurator to use a video encoder");
535 
536  let PARnum = {}, PARdenom = {}, FPSnum = {}, FPSdenom = {};
537  configurator.videoFormat.getVideoPAR(PARnum, PARdenom);
538  configurator.videoFormat.getVideoFrameRate(FPSnum, FPSdenom);
539 
540  // dump out the configurated video format for easier debugging
541  let props = [];
542  let propEnum = configurator.videoEncoderProperties.enumerator;
543  for each (let prop in ArrayConverter.JSEnum(propEnum)) {
544  if (prop instanceof Ci.nsIProperty) {
545  props.push(prop.name + ": " + prop.value);
546  }
547  }
548  log("Configurated video format: " +
549  configurator.videoFormat.videoWidth +
550  "x" + configurator.videoFormat.videoHeight +
551  " PAR " + PARnum.value + ":" + PARdenom.value +
552  " FPS " + FPSnum.value + ":" + FPSdenom.value +
553  " props: {" + props.join(", ") + "}");
554 
555  // check the video format
556  assertEqual(testcase.output.videoFormat.width,
557  configurator.videoFormat.videoWidth,
558  "video format width unexpected");
559  assertEqual(testcase.output.videoFormat.height,
560  configurator.videoFormat.videoHeight,
561  "video format height unexpected");
562  assertEqual(testcase.output.videoFormat.PAR.numerator,
563  PARnum.value,
564  "video format PAR numerator unexpected");
565  assertEqual(testcase.output.videoFormat.PAR.denominator,
566  PARdenom.value,
567  "video format PAR denominator unexpected");
568  assertEqual(testcase.output.videoFormat.frameRate.numerator,
569  FPSnum.value,
570  "video format frame rate numerator unexpected");
571  assertEqual(testcase.output.videoFormat.frameRate.denominator,
572  FPSdenom.value,
573  "video format frame rate denominator unexpected");
574 
575  assertTrue(configurator.videoEncoderProperties instanceof Ci.nsIPropertyBag2);
576  for (let prop in testcase.output.videoFormat.properties) {
577  if (testcase.output.videoFormat.properties[prop] instanceof Function) {
578  let f = testcase.output.videoFormat.properties[prop];
579  let val = configurator.videoEncoderProperties.get(prop);
580  assertTrue(f.call(testcase, val));
581  }
582  else {
583  assertEqual(testcase.output.videoFormat.properties[prop],
584  configurator.videoEncoderProperties.get(prop),
585  "video property " + prop + " mismatch");
586  }
587  }
588  }
589  else {
590  assertFalse(configurator.useVideoEncoder,
591  "expected configurator to not use a video encoder");
592  }
593 
594  // check the audio encoder
595  if (testcase.output.audioEncoder != null) {
596  assertEqual(testcase.output.audioEncoder,
597  configurator.audioEncoder,
598  "expected audio encoder is not configured audio encoder");
599  assertTrue(configurator.useAudioEncoder,
600  "expected configurator to use an audio encoder");
601 
602  // dump out the configurated audio format for easier debugging
603  let props = [];
604  let propEnum = configurator.audioEncoderProperties.enumerator;
605  for each (let prop in ArrayConverter.JSEnum(propEnum)) {
606  if (prop instanceof Ci.nsIProperty) {
607  props.push(prop.name + ": " + prop.value);
608  }
609  }
610  log("Configurated audio format: " +
611  configurator.audioFormat.sampleRate + "Hz " +
612  configurator.audioFormat.channels + "ch" +
613  " props: {" + props.join(", ") + "}");
614 
615  // check the audio format
616  assertEqual(testcase.output.audioFormat.sampleRate,
617  configurator.audioFormat.sampleRate,
618  "audio format sample rate unexpected");
619  assertEqual(testcase.output.audioFormat.channels,
620  configurator.audioFormat.channels,
621  "audio format channels unexpected");
622 
623  assertTrue(configurator.audioEncoderProperties instanceof Ci.nsIPropertyBag2);
624  for (let prop in testcase.output.audioFormat.properties) {
625  assertEqual(testcase.output.audioFormat.properties[prop],
626  configurator.audioEncoderProperties.get(prop),
627  "video property " + prop + " mismatch");
628  }
629  }
630  else {
631  assertFalse(configurator.useAudioEncoder,
632  "expected configurator to not use an audio encoder");
633  }
634  }
635 
636  return;
637 }
function checkBitrate(aCaps, aBitrate, aMin, aMax)
const Cc
function F(aNumerator, aDenominator)
Shorthand for creating a fraction.
function log(s)
sbOSDControlService prototype QueryInterface
function assertTrue(aTest, aMessage)
function sbDevCapRange(aInput)
Make a sbIDevCapRange The input can either be an array (in which case it's used as explicit values) o...
function assertEqual(aExpected, aActual, aMessage)
function width(ele) rect(ele).width
function CopyProperties(aPropertyBag)
Create a property bag, and do a shallow copy of another property bag if given.
function runTest()
Advanced DataRemote unit tests.
function fromFractionRange(aFractions)
this _dialogInput val(dateText)
return null
Definition: FeedWriter.js:1143
function createLibrary(databaseGuid, databaseLocation)
Definition: test_load.js:151
_updateDatepicker height
function newURI(aURLString)
function assertFalse(aTest, aMessage)
const Ci
#define min(a, b)
function range(x, y)
Definition: httpd.js:138