sbGStreamerTranscode.cpp
Go to the documentation of this file.
1 /*
2 //
3 // BEGIN SONGBIRD GPL
4 //
5 // This file is part of the Songbird web player.
6 //
7 // Copyright(c) 2005-2009 POTI, Inc.
8 // http://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 */
26 
27 #include "sbGStreamerTranscode.h"
28 
29 #include <sbIGStreamerService.h>
30 
31 #include <sbStringUtils.h>
32 #include <sbClassInfoUtils.h>
34 #include <sbMemoryUtils.h>
35 
36 #include <nsServiceManagerUtils.h>
37 #include <nsThreadUtils.h>
38 #include <nsStringAPI.h>
39 #include <nsArrayUtils.h>
40 #include <nsNetUtil.h>
41 
42 #include <nsIFile.h>
43 #include <nsIURI.h>
44 #include <nsIFileURL.h>
45 #include <nsIBinaryInputStream.h>
46 
47 #include <gst/tag/tag.h>
48 
49 #include <prlog.h>
50 
51 #define PROGRESS_INTERVAL 200 /* milliseconds */
52 
57 #ifdef PR_LOGGING
58 static PRLogModuleInfo* gGStreamerTranscode = PR_NewLogModule("sbGStreamerTranscode");
59 #define LOG(args) PR_LOG(gGStreamerTranscode, PR_LOG_WARNING, args)
60 #define TRACE(args) PR_LOG(gGStreamerTranscode, PR_LOG_DEBUG, args)
61 #else /* PR_LOGGING */
62 #define LOG(args) /* nothing */
63 #define TRACE(args) /* nothing */
64 #endif /* PR_LOGGING */
65 
75 
83 
84 NS_DECL_CLASSINFO(sbGStreamerTranscode);
85 NS_IMPL_THREADSAFE_CI(sbGStreamerTranscode);
86 
87 sbGStreamerTranscode::sbGStreamerTranscode() :
89  mStatus(sbIJobProgress::STATUS_RUNNING) // There is no NOT_STARTED
90 {
91 }
92 
93 sbGStreamerTranscode::~sbGStreamerTranscode()
94 {
95 }
96 
97 /* nsITimerCallback interface implementation */
98 
99 NS_IMETHODIMP
100 sbGStreamerTranscode::Notify(nsITimer *aTimer)
101 {
102  NS_ENSURE_ARG_POINTER(aTimer);
103 
104  OnJobProgress();
105 
106  return NS_OK;
107 }
108 
109 /* sbITranscodeJob interface implementation */
110 
111 NS_IMETHODIMP
112 sbGStreamerTranscode::SetSourceURI(const nsAString& aSourceURI)
113 {
114  mSourceURI = aSourceURI;
115 
116  // Use the source URI as the resource name too.
117  mResourceDisplayName = mSourceURI;
118  return NS_OK;
119 }
120 
121 NS_IMETHODIMP
122 sbGStreamerTranscode::GetSourceURI(nsAString& aSourceURI)
123 {
124  aSourceURI = mSourceURI;
125  return NS_OK;
126 }
127 
128 NS_IMETHODIMP
129 sbGStreamerTranscode::SetDestURI(const nsAString& aDestURI)
130 {
131  mDestURI = aDestURI;
132  return NS_OK;
133 }
134 
135 NS_IMETHODIMP
136 sbGStreamerTranscode::GetDestURI(nsAString& aDestURI)
137 {
138  aDestURI = mDestURI;
139  return NS_OK;
140 }
141 
142 NS_IMETHODIMP
143 sbGStreamerTranscode::SetProfile(sbITranscodeProfile *aProfile)
144 {
145  NS_ENSURE_ARG_POINTER(aProfile);
146 
147  mProfile = aProfile;
148  return NS_OK;
149 }
150 
151 NS_IMETHODIMP
152 sbGStreamerTranscode::GetProfile(sbITranscodeProfile **aProfile)
153 {
154  NS_ENSURE_ARG_POINTER(aProfile);
155 
156  NS_IF_ADDREF(*aProfile = mProfile);
157  return NS_OK;
158 }
159 
160 NS_IMETHODIMP
161 sbGStreamerTranscode::GetMetadata(sbIPropertyArray **aMetadata)
162 {
163  NS_ENSURE_ARG_POINTER(aMetadata);
164 
165  NS_IF_ADDREF(*aMetadata = mMetadata);
166  return NS_OK;
167 }
168 
169 NS_IMETHODIMP
170 sbGStreamerTranscode::SetMetadata(sbIPropertyArray *aMetadata)
171 {
172  mMetadata = aMetadata;
173  return NS_OK;
174 }
175 
176 NS_IMETHODIMP
177 sbGStreamerTranscode::GetMetadataImage(nsIInputStream **aImageStream)
178 {
179  NS_ENSURE_ARG_POINTER(aImageStream);
180 
181  NS_IF_ADDREF(*aImageStream = mImageStream);
182  return NS_OK;
183 }
184 
185 NS_IMETHODIMP
186 sbGStreamerTranscode::SetMetadataImage(nsIInputStream *aImageStream)
187 {
188  mImageStream = aImageStream;
189  return NS_OK;
190 }
191 
192 GstElement *
193 sbGStreamerTranscode::BuildTranscodePipeline(sbITranscodeProfile *aProfile)
194 {
195  nsCString pipelineString;
196  nsCString pipelineDescription;
197  GError *error = NULL;
198  GstElement *pipeline;
199  nsresult rv;
200 
201  rv = BuildPipelineFragmentFromProfile(aProfile, pipelineDescription);
202  NS_ENSURE_SUCCESS (rv, NULL);
203 
204  rv = BuildPipelineString(pipelineDescription, pipelineString);
205  NS_ENSURE_SUCCESS (rv, NULL);
206 
207  //ctx = gst_parse_context_new();
208  //mPipeline = gst_parse_launch_full (pipelineString.BeginReading(), ctx,
209  // GST_PARSE_FLAG_FATAL_ERRORS, &error);
210  pipeline = gst_parse_launch (pipelineString.BeginReading(), &error);
211 
212  return pipeline;
213 }
214 
215 nsresult
216 sbGStreamerTranscode::AddImageToTagList(GstTagList *aTags,
217  nsIInputStream *aStream)
218 {
219  PRUint32 imageDataLen;
220  PRUint8 *imageData;
221  nsresult rv;
222 
223  nsCOMPtr<nsIBinaryInputStream> stream =
224  do_CreateInstance("@mozilla.org/binaryinputstream;1", &rv);
225  NS_ENSURE_SUCCESS(rv, rv);
226 
227  rv = stream->SetInputStream(aStream);
228  NS_ENSURE_SUCCESS(rv, rv);
229 
230  rv = aStream->Available(&imageDataLen);
231  NS_ENSURE_SUCCESS(rv, rv);
232 
233  rv = stream->ReadByteArray(imageDataLen, &imageData);
234  NS_ENSURE_SUCCESS(rv, rv);
235 
236  sbAutoNSMemPtr imageDataDestroy(imageData);
237 
238  GstBuffer *imagebuf = gst_tag_image_data_to_image_buffer (
239  imageData, imageDataLen, GST_TAG_IMAGE_TYPE_FRONT_COVER);
240  if (!imagebuf)
241  return NS_ERROR_FAILURE;
242 
243  gst_tag_list_add (aTags, GST_TAG_MERGE_REPLACE, GST_TAG_IMAGE,
244  imagebuf, NULL);
245  gst_buffer_unref (imagebuf);
246 
247  return NS_OK;
248 }
249 
250 nsresult
252 {
253  NS_ENSURE_STATE (mProfile);
254 
255  nsresult rv;
256  GstTagList *tags;
257 
258  mPipeline = BuildTranscodePipeline(mProfile);
259 
260  if (!mPipeline) {
261  // TODO: Report the error more usefully using the GError, and perhaps
262  // the GstParseContext.
263  rv = NS_ERROR_FAILURE;
264  goto done;
265  }
266 
267  // We have a pipeline, set its main operation to transcoding.
269 
270  tags = ConvertPropertyArrayToTagList(mMetadata);
271 
272  if (mImageStream) {
273  // Ignore return value here, failure is not critical.
274  AddImageToTagList (tags, mImageStream);
275  }
276 
277  if (tags) {
278  // Find all the tag setters in the pipeline
279  GstIterator *it = gst_bin_iterate_all_by_interface (
280  (GstBin *)mPipeline, GST_TYPE_TAG_SETTER);
281  GstElement *element;
282 
283  while (gst_iterator_next (it, (void **)&element) == GST_ITERATOR_OK) {
284  GstTagSetter *setter = GST_TAG_SETTER (element);
285 
286  /* Use MERGE_REPLACE: preserves existing tag where we don't have one
287  * in our taglist */
288  gst_tag_setter_merge_tags (setter, tags, GST_TAG_MERGE_REPLACE);
289  g_object_unref (element);
290  }
291  gst_iterator_free (it);
292  gst_tag_list_free (tags);
293  }
294 
295  rv = NS_OK;
296 done:
297  //gst_parse_context_free (ctx);
298 
299  return rv;
300 }
301 
302 NS_IMETHODIMP
303 sbGStreamerTranscode::Vote(sbIMediaItem *aMediaItem,
304  sbITranscodeProfile *aProfile, PRInt32 *aVote)
305 {
306  NS_ENSURE_ARG_POINTER(aVote);
307 
308  GstElement *pipeline = BuildTranscodePipeline(aProfile);
309  if (!pipeline) {
310  /* Couldn't create a pipeline for this profile; do not accept it */
311  *aVote = -1;
312  }
313  else {
314  /* For now just vote 1 for anything we can handle */
315  gst_object_unref (pipeline);
316  *aVote = 1;
317  }
318 
319  return NS_OK;
320 }
321 
322 NS_IMETHODIMP
323 sbGStreamerTranscode::Transcode()
324 {
325  return PlayPipeline();
326 }
327 
328 // Override base class to start the progress reporter
329 NS_IMETHODIMP
331 {
332  nsresult rv;
333 
334  rv = sbGStreamerPipeline::PlayPipeline();
335  NS_ENSURE_SUCCESS (rv, rv);
336 
337  rv = StartProgressReporting();
338  NS_ENSURE_SUCCESS (rv, rv);
339 
340  return NS_OK;
341 }
342 
343 // Override base class to stop the progress reporter
344 NS_IMETHODIMP
346 {
347  nsresult rv;
348 
349  rv = sbGStreamerPipeline::StopPipeline();
350  NS_ENSURE_SUCCESS (rv, rv);
351 
352  rv = StopProgressReporting();
353  NS_ENSURE_SUCCESS (rv, rv);
354 
355  // Inform listeners of new job status
356  rv = OnJobProgress();
357  NS_ENSURE_SUCCESS (rv, rv);
358 
359  return NS_OK;
360 }
361 
362 /* sbIJobCancelable interface implementation */
363 
364 NS_IMETHODIMP
365 sbGStreamerTranscode::GetCanCancel(PRBool *aCanCancel)
366 {
367  NS_ENSURE_ARG_POINTER(aCanCancel);
368 
369  *aCanCancel = PR_TRUE;
370  return NS_OK;
371 }
372 
373 NS_IMETHODIMP
374 sbGStreamerTranscode::Cancel()
375 {
376  mStatus = sbIJobProgress::STATUS_FAILED; // We don't have a 'cancelled' state.
377 
378  nsresult rv = StopPipeline();
379  NS_ENSURE_SUCCESS (rv, rv);
380 
381  return NS_OK;
382 }
383 
384 /* sbIJobProgress interface implementation */
385 
386 NS_IMETHODIMP
387 sbGStreamerTranscode::GetElapsedTime(PRUint32 *aElapsedTime)
388 {
389  NS_ENSURE_ARG_POINTER(aElapsedTime);
390 
391  /* Get the running time, and convert to milliseconds */
392  *aElapsedTime = static_cast<PRUint32>(GetRunningTime() / GST_MSECOND);
393 
394  return NS_OK;
395 }
396 
397 NS_IMETHODIMP
398 sbGStreamerTranscode::GetRemainingTime(PRUint32 *aRemainingTime)
399 {
400  GstClockTime duration = QueryDuration();
401  GstClockTime position = QueryPosition();
402  GstClockTime elapsed = GetRunningTime();
403 
404  if (duration == GST_CLOCK_TIME_NONE || position == GST_CLOCK_TIME_NONE ||
405  elapsed == GST_CLOCK_TIME_NONE)
406  {
407  /* Unknown, so set to -1 */
408  *aRemainingTime = (PRUint32)-1;
409  }
410  else {
411  GstClockTime totalTime = gst_util_uint64_scale (elapsed, duration,
412  position);
413  /* Convert to milliseconds */
414  *aRemainingTime =
415  static_cast<PRUint32>((totalTime - elapsed) / GST_MSECOND);
416  }
417 
418  return NS_OK;
419 }
420 
421 NS_IMETHODIMP
422 sbGStreamerTranscode::GetStatus(PRUint16 *aStatus)
423 {
424  NS_ENSURE_ARG_POINTER(aStatus);
425 
426  *aStatus = mStatus;
427 
428  return NS_OK;
429 }
430 
431 NS_IMETHODIMP
432 sbGStreamerTranscode::GetBlocked(PRBool *aBlocked)
433 {
434  NS_ENSURE_ARG_POINTER(aBlocked);
435 
436  *aBlocked = PR_FALSE;
437 
438  return NS_OK;
439 }
440 
441 NS_IMETHODIMP
442 sbGStreamerTranscode::GetStatusText(nsAString& aText)
443 {
444  nsresult rv = NS_ERROR_FAILURE;
445 
446  switch (mStatus) {
448  rv = SBGetLocalizedString(aText,
449  NS_LITERAL_STRING("mediacore.gstreamer.transcode.failed"));
450  break;
452  rv = SBGetLocalizedString(aText,
453  NS_LITERAL_STRING("mediacore.gstreamer.transcode.succeeded"));
454  break;
456  rv = SBGetLocalizedString(aText,
457  NS_LITERAL_STRING("mediacore.gstreamer.transcode.running"));
458  break;
459  default:
460  NS_NOTREACHED("Status is invalid");
461  }
462 
463  return rv;
464 }
465 
466 NS_IMETHODIMP
467 sbGStreamerTranscode::GetTitleText(nsAString& aText)
468 {
469  return SBGetLocalizedString(aText,
470  NS_LITERAL_STRING("mediacore.gstreamer.transcode.title"));
471 }
472 
473 NS_IMETHODIMP
474 sbGStreamerTranscode::GetProgress(PRUint32* aProgress)
475 {
476  NS_ENSURE_ARG_POINTER(aProgress);
477 
478  GstClockTime duration = QueryDuration();
479  GstClockTime position = QueryPosition();
480 
481  if (duration != GST_CLOCK_TIME_NONE && position != GST_CLOCK_TIME_NONE &&
482  duration != 0)
483  *aProgress = (PRUint32)gst_util_uint64_scale (position, 1000, duration);
484  else
485  *aProgress = 0; // Unknown
486 
487  return NS_OK;
488 }
489 
490 NS_IMETHODIMP
491 sbGStreamerTranscode::GetTotal(PRUint32* aTotal)
492 {
493  NS_ENSURE_ARG_POINTER(aTotal);
494 
495  GstClockTime duration = QueryDuration();
496 
497  // The job progress stuff doesn't like overly large numbers, so we artifically
498  // fix it to a max of 1000.
499 
500  if (duration != GST_CLOCK_TIME_NONE)
501  *aTotal = 1000;
502  else
503  *aTotal = 0;
504 
505  return NS_OK;
506 }
507 
508 // Note that you can also get errors reported via the mediacore listener
509 // interfaces.
510 NS_IMETHODIMP
511 sbGStreamerTranscode::GetErrorCount(PRUint32* aErrorCount)
512 {
513  NS_ENSURE_ARG_POINTER(aErrorCount);
514  NS_ASSERTION(NS_IsMainThread(),
515  "sbIJobProgress::GetErrorCount is main thread only!");
516 
517  *aErrorCount = mErrorMessages.Length();
518 
519  return NS_OK;
520 }
521 
522 NS_IMETHODIMP
523 sbGStreamerTranscode::GetErrorMessages(nsIStringEnumerator** aMessages)
524 {
525  NS_ENSURE_ARG_POINTER(aMessages);
526  NS_ASSERTION(NS_IsMainThread(),
527  "sbIJobProgress::GetProgress is main thread only!");
528 
529  *aMessages = nsnull;
530 
531  nsCOMPtr<nsIStringEnumerator> enumerator =
532  new sbTArrayStringEnumerator(&mErrorMessages);
533  NS_ENSURE_TRUE(enumerator, NS_ERROR_OUT_OF_MEMORY);
534 
535  enumerator.forget(aMessages);
536  return NS_OK;
537 }
538 
539 NS_IMETHODIMP
540 sbGStreamerTranscode::AddJobProgressListener(sbIJobProgressListener *aListener)
541 {
542  NS_ENSURE_ARG_POINTER(aListener);
543  NS_ASSERTION(NS_IsMainThread(), \
544  "sbGStreamerTranscode::AddJobProgressListener is main thread only!");
545 
546  PRInt32 index = mProgressListeners.IndexOf(aListener);
547  if (index >= 0) {
548  // the listener already exists, do not re-add
549  return NS_SUCCESS_LOSS_OF_INSIGNIFICANT_DATA;
550  }
551  PRBool succeeded = mProgressListeners.AppendObject(aListener);
552  NS_ENSURE_TRUE(succeeded, NS_ERROR_FAILURE);
553 
554  return NS_OK;
555 }
556 
557 NS_IMETHODIMP
558 sbGStreamerTranscode::RemoveJobProgressListener(
559  sbIJobProgressListener* aListener)
560 {
561  NS_ENSURE_ARG_POINTER(aListener);
562  NS_ASSERTION(NS_IsMainThread(), \
563  "sbGStreamerTranscode::RemoveJobProgressListener is main thread only!");
564 
565  PRInt32 indexToRemove = mProgressListeners.IndexOf(aListener);
566  if (indexToRemove < 0) {
567  // No such listener, don't try to remove. This is OK.
568  return NS_OK;
569  }
570 
571  // remove the listener
572  PRBool succeeded = mProgressListeners.RemoveObjectAt(indexToRemove);
573  NS_ENSURE_TRUE(succeeded, NS_ERROR_FAILURE);
574 
575  return NS_OK;
576 }
577 
578 // Call all job progress listeners
579 nsresult
580 sbGStreamerTranscode::OnJobProgress()
581 {
582  TRACE(("sbGStreamerTranscode::OnJobProgress[0x%.8x]", this));
583  NS_ASSERTION(NS_IsMainThread(), \
584  "sbGStreamerTranscode::OnJobProgress is main thread only!");
585 
586  // Announce our status to the world
587  for (PRInt32 i = mProgressListeners.Count() - 1; i >= 0; --i) {
588  // Ignore any errors from listeners
589  mProgressListeners[i]->OnJobProgress(this);
590  }
591  return NS_OK;
592 }
593 
594 void sbGStreamerTranscode::HandleErrorMessage(GstMessage *message)
595 {
596  GError *gerror = NULL;
597  gchar *debug = NULL;
598 
600 
601  gst_message_parse_error(message, &gerror, &debug);
602 
603  mErrorMessages.AppendElement(
604  NS_ConvertUTF8toUTF16(nsDependentCString(gerror->message)));
605 
606  g_error_free (gerror);
607  g_free(debug);
608 
609  // This will stop the pipeline and update listeners
611 }
612 
613 void sbGStreamerTranscode::HandleEOSMessage(GstMessage *message)
614 {
615  TRACE(("sbGStreamerTranscode::HandleEOSMessage[0x%.8x]", this));
616 
618 
619  // This will stop the pipeline and update listeners
621 }
622 
623 GstClockTime sbGStreamerTranscode::QueryPosition()
624 {
625  GstQuery *query;
626  gint64 position = GST_CLOCK_TIME_NONE;
627 
628  if (!mPipeline)
629  return position;
630 
631  query = gst_query_new_position(GST_FORMAT_TIME);
632 
633  if (gst_element_query(mPipeline, query))
634  gst_query_parse_position(query, NULL, &position);
635 
636  gst_query_unref (query);
637 
638  return position;
639 }
640 
641 GstClockTime sbGStreamerTranscode::QueryDuration()
642 {
643  GstQuery *query;
644  gint64 duration = GST_CLOCK_TIME_NONE;
645 
646  if (!mPipeline)
647  return duration;
648 
649  query = gst_query_new_duration(GST_FORMAT_TIME);
650 
651  if (gst_element_query(mPipeline, query))
652  gst_query_parse_duration(query, NULL, &duration);
653 
654  gst_query_unref (query);
655 
656  return duration;
657 }
658 
659 nsresult
660 sbGStreamerTranscode::StartProgressReporting()
661 {
662  NS_ENSURE_STATE(!mProgressTimer);
663  TRACE(("sbGStreamerTranscode::StartProgressReporting[0x%.8x]", this));
664 
665  nsresult rv;
666  mProgressTimer =
667  do_CreateInstance("@mozilla.org/timer;1", &rv);
668  NS_ENSURE_SUCCESS(rv, rv);
669 
670  mProgressTimer->InitWithCallback(this,
671  PROGRESS_INTERVAL, nsITimer::TYPE_REPEATING_SLACK);
672 
673  return NS_OK;
674 }
675 
676 nsresult
677 sbGStreamerTranscode::StopProgressReporting()
678 {
679  TRACE(("sbGStreamerTranscode::StopProgressReporting[0x%.8x]", this));
680 
681  if (mProgressTimer) {
682  mProgressTimer->Cancel();
683  mProgressTimer = nsnull;
684  }
685 
686  return NS_OK;
687 }
688 
689 nsresult
690 sbGStreamerTranscode::BuildPipelineString(nsCString pipelineDescription,
691  nsACString &pipeline)
692 {
693  // Build a pipeline description string looking something like:
694  //
695  // file:///tmp/test.mp3 ! decodebin ! audioconvert ! audioresample !
696  // vorbisenc ! oggmux ! file://tmp/test.ogg
697  //
698  // The URIs come from mSourceURI, mDestURI, the 'vorbisenc ! oggmux' bit
699  // from the pipeline string.
700  // We may add a configurable capsfilter later, but this might be enough for
701  // the moment...
702 
703  pipeline.Append(NS_ConvertUTF16toUTF8(mSourceURI));
704  pipeline.AppendLiteral(" ! decodebin ! audioconvert ! audioresample ! ");
705  pipeline.Append(pipelineDescription);
706  pipeline.AppendLiteral(" ! ");
707  pipeline.Append(NS_ConvertUTF16toUTF8(mDestURI));
708 
709  LOG(("Built pipeline string: '%s'", pipeline.BeginReading()));
710 
711  return NS_OK;
712 }
713 
714 nsresult
715 sbGStreamerTranscode::BuildPipelineFragmentFromProfile(
716  sbITranscodeProfile *aProfile, nsACString &pipelineFragment)
717 {
718  NS_ENSURE_ARG_POINTER(aProfile);
719 
720  nsresult rv;
721  PRUint32 type;
722  nsString container;
723  nsString audioCodec;
724  nsCString gstContainerMuxer;
725  nsCString gstAudioEncoder;
726  nsCOMPtr<nsIArray> containerProperties;
727  nsCOMPtr<nsIArray> audioProperties;
728 
729  rv = aProfile->GetType(&type);
730  NS_ENSURE_SUCCESS (rv, rv);
731 
732  rv = aProfile->GetContainerFormat(container);
733  NS_ENSURE_SUCCESS (rv, rv);
734 
735  rv = aProfile->GetContainerProperties(getter_AddRefs(containerProperties));
736  NS_ENSURE_SUCCESS (rv, rv);
737 
738  rv = aProfile->GetAudioCodec(audioCodec);
739  NS_ENSURE_SUCCESS (rv, rv);
740 
741  rv = aProfile->GetAudioProperties(getter_AddRefs(audioProperties));
742  NS_ENSURE_SUCCESS (rv, rv);
743 
745  return NS_ERROR_FAILURE;
746 
747  /* Each of container and audio codec is optional */
748  if (!audioCodec.IsEmpty()) {
749  rv = GetAudioCodec(audioCodec, audioProperties, gstAudioEncoder);
750  NS_ENSURE_SUCCESS (rv, rv);
751 
752  pipelineFragment.Append(gstAudioEncoder);
753  }
754 
755  if (!container.IsEmpty()) {
756  rv = GetContainer(container, containerProperties, gstContainerMuxer);
757  NS_ENSURE_SUCCESS (rv, rv);
758 
759  pipelineFragment.AppendLiteral(" ! ");
760  pipelineFragment.Append(gstContainerMuxer);
761  }
762 
763  return NS_OK;
764 }
765 
766 NS_IMETHODIMP
767 sbGStreamerTranscode::EstimateOutputSize(PRInt32 inputDuration,
768  PRInt64 *_retval)
769 {
770  /* It's pretty hard to do anything really accurate here.
771  Note that we're currently ONLY doing audio transcoding, which simplifies
772  things.
773  Current approach
774  - Calculate bitrate of audio.
775  - if there's a bitrate property, assume it's in bits per second, and is
776  roughly accurate
777  - otherwise, assume we're encoding to a lossless format such as FLAC.
778  Typically, those get an average compression ratio of a little better
779  than 0.6 (for 44.1kHz 16-bit stereo content). Assume that's what
780  we have; this will sometimes be WAY off, but we can't really do
781  any better.
782  - Calculate size of audio data based on this and the duration
783  - Add 5% for container/etc overhead.
784  */
785  NS_ENSURE_STATE (mProfile);
786  NS_ENSURE_ARG_POINTER(_retval);
787 
788  PRUint32 bitrate = 0;
789  nsresult rv;
790  nsCOMPtr<nsIArray> audioProperties;
791 
792  rv = mProfile->GetAudioProperties(getter_AddRefs(audioProperties));
793  NS_ENSURE_SUCCESS (rv, rv);
794 
795  PRUint32 propertiesLength = 0;
796  rv = audioProperties->GetLength(&propertiesLength);
797  NS_ENSURE_SUCCESS (rv, rv);
798 
799  for (PRUint32 j = 0; j < propertiesLength; j++) {
800  nsCOMPtr<sbITranscodeProfileProperty> property =
801  do_QueryElementAt(audioProperties, j, &rv);
802  NS_ENSURE_SUCCESS(rv, rv);
803 
804  nsString propName;
805  rv = property->GetPropertyName(propName);
806  NS_ENSURE_SUCCESS(rv, rv);
807 
808  if (propName.EqualsLiteral("bitrate")) {
809  nsCOMPtr<nsIVariant> propValue;
810  rv = property->GetValue(getter_AddRefs(propValue));
811  NS_ENSURE_SUCCESS(rv, rv);
812 
813  PRUint32 bitrate;
814  rv = propValue->GetAsUint32(&bitrate);
815  NS_ENSURE_SUCCESS(rv, rv);
816 
817  break;
818  }
819  }
820 
821  if (bitrate == 0) {
822  // No bitrate found; assume something like FLAC as above. This is bad; how
823  // can we do better though?
824  bitrate = static_cast<PRUint32>(0.6 * (44100 * 2 * 16));
825  }
826 
827  /* Calculate size from duration (in ms) and bitrate (in bits/second) */
828  PRUint64 size = (PRUint64)inputDuration * bitrate / 8 / 1000;
829 
830  /* Add 5% for container overhead, etc. */
831  size += (size/20);
832 
833  *_retval = size;
834 
835  return NS_OK;
836 }
837 
838 struct GSTNameMap {
839  const char *name;
840  const char *gstCapsName;
841 };
842 
843 static struct GSTNameMap SupportedContainers[] = {
844  {"application/ogg", "application/ogg"},
845  {"audio/mpeg", "application/x-id3"},
846  {"video/x-ms-asf", "video/x-ms-asf"},
847  {"audio/x-wav", "audio/x-wav"},
848  {"video/mp4", "video/quicktime, variant=iso"},
849  {"video/3gpp", "video/quicktime, variant=(string)3gpp"}
850 };
851 
852 nsresult
853 sbGStreamerTranscode::GetContainer(nsAString &container, nsIArray *properties,
854  nsACString &gstMuxer)
855 {
856  nsCString cont = NS_ConvertUTF16toUTF8 (container);
857 
858  for (unsigned int i = 0;
859  i < sizeof (SupportedContainers)/sizeof(*SupportedContainers);
860  i++)
861  {
862  if (strcmp (cont.BeginReading(), SupportedContainers[i].name) == 0)
863  {
864  const char *capsString = SupportedContainers[i].gstCapsName;
865  const char *gstElementName = FindMatchingElementName (
866  capsString, "Muxer");
867  if (!gstElementName) {
868  // Muxers for 'tag formats' like id3 are sometimes named 'Formatter'
869  // rather than 'Muxer'. Search for that too.
870  gstElementName = FindMatchingElementName (capsString, "Formatter");
871  }
872 
873  if (!gstElementName)
874  continue;
875 
876  gstMuxer.Append(gstElementName);
877  /* Ignore properties for now, we don't have any we care about yet */
878  return NS_OK;
879  }
880  }
881 
882  return NS_ERROR_FAILURE;
883 }
884 
885 static struct GSTNameMap SupportedAudioCodecs[] = {
886  {"audio/x-vorbis", "audio/x-vorbis"},
887  {"audio/x-flac", "audio/x-flac"},
888  {"audio/x-ms-wma", "audio/x-wma, wmaversion=(int)2"},
889  {"audio/mpeg", "audio/mpeg, mpegversion=(int)1, layer=(int)3"},
890  {"audio/aac", "audio/mpeg, mpegversion=(int)4"},
891 };
892 
893 nsresult
894 sbGStreamerTranscode::GetAudioCodec(nsAString &aCodec, nsIArray *properties,
895  nsACString &gstCodec)
896 {
897  nsresult rv;
898  nsCString codec = NS_ConvertUTF16toUTF8 (aCodec);
899 
900  for (unsigned int i = 0;
901  i < sizeof (SupportedAudioCodecs)/sizeof(*SupportedAudioCodecs);
902  i++)
903  {
904  if (strcmp (codec.BeginReading(), SupportedAudioCodecs[i].name) == 0)
905  {
906  const char *capsString = SupportedAudioCodecs[i].gstCapsName;
907  const char *gstElementName = FindMatchingElementName (
908  capsString, "Encoder");
909  if (!gstElementName)
910  continue;
911 
912  gstCodec.Append(gstElementName);
913 
914  /* Now handle the properties */
915  PRUint32 propertiesLength = 0;
916  rv = properties->GetLength(&propertiesLength);
917  NS_ENSURE_SUCCESS (rv, rv);
918 
919  for (PRUint32 j = 0; j < propertiesLength; j++) {
920  nsCOMPtr<sbITranscodeProfileProperty> property =
921  do_QueryElementAt(properties, j, &rv);
922  NS_ENSURE_SUCCESS(rv, rv);
923 
924  nsString propName;
925  rv = property->GetPropertyName(propName);
926  NS_ENSURE_SUCCESS(rv, rv);
927 
928  nsCOMPtr<nsIVariant> propValue;
929  rv = property->GetValue(getter_AddRefs(propValue));
930  NS_ENSURE_SUCCESS(rv, rv);
931 
932  nsString propValueString;
933  rv = propValue->GetAsAString(propValueString);
934  NS_ENSURE_SUCCESS(rv, rv);
935 
936  /* Append the property now. Later, we might need to convert some
937  keys/values from 'generic' to gstreamer-element-specific ones,
938  but we have no examples of that yet */
939  gstCodec.AppendLiteral(" ");
940  gstCodec.Append(NS_ConvertUTF16toUTF8(propName));
941  gstCodec.AppendLiteral("=");
942  gstCodec.Append(NS_ConvertUTF16toUTF8(propValueString));
943 
944  }
945 
946  return NS_OK;
947  }
948  }
949 
950  return NS_ERROR_FAILURE;
951 }
952 
953 NS_IMETHODIMP
954 sbGStreamerTranscode::GetAvailableProfiles(nsIArray * *aAvailableProfiles)
955 {
956  if (mAvailableProfiles) {
957  NS_IF_ADDREF (*aAvailableProfiles = mAvailableProfiles);
958  return NS_OK;
959  }
960 
961  /* If we haven't already cached it, then figure out what we have */
962 
963  nsresult rv;
964  PRBool hasMoreElements;
965  nsCOMPtr<nsISimpleEnumerator> dirEnum;
966 
967  nsCOMPtr<nsIURI> profilesDirURI;
968  rv = NS_NewURI(getter_AddRefs(profilesDirURI),
969  NS_LITERAL_STRING("resource://app/gstreamer/encode-profiles"));
970  NS_ENSURE_SUCCESS(rv, rv);
971 
972  nsCOMPtr<nsIFileURL> profilesDirFileURL =
973  do_QueryInterface(profilesDirURI, &rv);
974  NS_ENSURE_SUCCESS(rv, rv);
975 
976  nsCOMPtr<nsIFile> profilesDir;
977  rv = profilesDirFileURL->GetFile(getter_AddRefs(profilesDir));
978  NS_ENSURE_SUCCESS(rv, rv);
979 
980  nsCOMPtr<nsIMutableArray> array =
981  do_CreateInstance("@songbirdnest.com/moz/xpcom/threadsafe-array;1", &rv);
982  NS_ENSURE_SUCCESS(rv, rv);
983 
984  nsCOMPtr<sbITranscodeProfileLoader> profileLoader =
985  do_CreateInstance("@songbirdnest.com/Songbird/Transcode/ProfileLoader;1",
986  &rv);
987  NS_ENSURE_SUCCESS (rv, rv);
988 
989  rv = profilesDir->GetDirectoryEntries(getter_AddRefs(dirEnum));
990  NS_ENSURE_SUCCESS (rv, rv);
991 
992  while (PR_TRUE) {
993  rv = dirEnum->HasMoreElements(&hasMoreElements);
994  NS_ENSURE_SUCCESS(rv, rv);
995  if (!hasMoreElements)
996  break;
997 
998  nsCOMPtr<nsIFile> file;
999  rv = dirEnum->GetNext(getter_AddRefs(file));
1000  NS_ENSURE_SUCCESS(rv, rv);
1001 
1002  nsCOMPtr<sbITranscodeProfile> profile;
1003 
1004  rv = profileLoader->LoadProfile(file, getter_AddRefs(profile));
1005  if (NS_FAILED(rv))
1006  continue;
1007 
1008  GstElement *pipeline = BuildTranscodePipeline(profile);
1009  if (!pipeline) {
1010  // Not able to use this profile; don't return it.
1011  continue;
1012  }
1013 
1014  // Don't actually want the pipeline, discard it.
1015  gst_object_unref (pipeline);
1016 
1017  rv = array->AppendElement(profile, PR_FALSE);
1018  NS_ENSURE_SUCCESS (rv, rv);
1019  }
1020 
1021  mAvailableProfiles = do_QueryInterface(array, &rv);
1022  NS_ENSURE_SUCCESS (rv, rv);
1023 
1024  NS_ADDREF(*aAvailableProfiles = mAvailableProfiles);
1025 
1026  return NS_OK;
1027 }
1028 
return NS_OK
inArray array
function succeeded(ch, cx, status, data)
Generic interface for exposing long running jobs to the UI.
static struct GSTNameMap SupportedAudioCodecs[]
NS_INTERFACE_MAP_END NS_IMPL_CI_INTERFACE_GETTER6(sbDeviceLibrary, nsIClassInfo, sbIDeviceLibrary, sbILibrary, sbIMediaList, sbIMediaItem, sbILibraryResource) sbDeviceLibrary
const unsigned long TRANSCODE_TYPE_AUDIO
An object defining a transcoding profile.
const unsigned short STATUS_SUCCEEDED
Constant indicating that the job has completed.
virtual nsresult BuildPipeline()
Generic interface extending sbIJobProgress that can track expected time, etc in addition to abstract ...
virtual void HandleErrorMessage(GstMessage *message)
attribute sbITranscodeProfile profile
The encoding profile to use.
static struct GSTNameMap SupportedContainers[]
const unsigned short STATUS_RUNNING
Constant indicating that the job is active.
NS_IMPL_THREADSAFE_CI(sbGStreamerTranscode)
const char * propName
sbIJobCancelable NS_DECL_CLASSINFO(sbGStreamerTranscode)
_hideDatepicker duration
GstTagList * ConvertPropertyArrayToTagList(sbIPropertyArray *properties)
unique done
virtual void HandleEOSMessage(GstMessage *message)
GstMessage * message
nsresult SBGetLocalizedString(nsAString &aString, const nsAString &aKey, const nsAString &aDefault, class nsIStringBundle *aStringBundle)
NS_IMPL_THREADSAFE_ISUPPORTS8(sbGStreamerTranscode, nsIClassInfo, sbIGStreamerPipeline, sbITranscodeJob, sbIMediacoreEventTarget, sbIJobProgress, sbIJobProgressTime, sbIJobCancelable, nsITimerCallback) NS_IMPL_CI_INTERFACE_GETTER6(sbGStreamerTranscode
SimpleArrayEnumerator prototype hasMoreElements
#define TRACE(args)
An object capable of transcoding a source URI to a destination file.
function debug(aMsg)
#define LOG(args)
void SetPipelineOp(GStreamer::pipelineOp_t aPipelineOp)
Implemented to receive notifications from sbIJobProgress interfaces.
Interface that defines a single item of media in the system.
const char * gstCapsName
#define PROGRESS_INTERVAL
const unsigned short STATUS_FAILED
Constant indicating that the job has completed with errors.
An interface to carry around arrays of nsIProperty instances. Users of this interface should only QI ...
_getSelectedPageStyle s i
nsITimerCallback
const char * FindMatchingElementName(const char *srcCapsString, const char *typeName)
var file