зеркало из https://github.com/mozilla/gecko-dev.git
Bug 842243 - Part 5: Add MediaEncoder.h/.cpp; The main part of this media encoding framework. r=roc
This commit is contained in:
Родитель
d409f83794
Коммит
06a22765c7
|
@ -0,0 +1,242 @@
|
|||
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-*/
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
* You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
#include "MediaEncoder.h"
|
||||
#include "MediaDecoder.h"
|
||||
#ifdef MOZ_OGG
|
||||
#include "OggWriter.h"
|
||||
#endif
|
||||
#ifdef MOZ_OPUS
|
||||
#include "OpusTrackEncoder.h"
|
||||
#endif
|
||||
|
||||
#ifdef MOZ_WIDGET_GONK
|
||||
#include <android/log.h>
|
||||
#define LOG(args...) __android_log_print(ANDROID_LOG_INFO, "MediaEncoder", ## args);
|
||||
#else
|
||||
#define LOG(args,...)
|
||||
#endif
|
||||
|
||||
namespace mozilla {
|
||||
|
||||
#define TRACK_BUFFER_LEN 8192
|
||||
|
||||
namespace {
|
||||
|
||||
template <class String>
|
||||
static bool
|
||||
TypeListContains(char const *const * aTypes, const String& aType)
|
||||
{
|
||||
for (int32_t i = 0; aTypes[i]; ++i) {
|
||||
if (aType.EqualsASCII(aTypes[i]))
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
#ifdef MOZ_OGG
|
||||
// The recommended mime-type for Ogg Opus files is audio/ogg.
|
||||
// See http://wiki.xiph.org/OggOpus for more details.
|
||||
static const char* const gOggTypes[2] = {
|
||||
"audio/ogg",
|
||||
nullptr
|
||||
};
|
||||
|
||||
static bool
|
||||
IsOggType(const nsAString& aType)
|
||||
{
|
||||
if (!MediaDecoder::IsOggEnabled()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return TypeListContains(gOggTypes, aType);
|
||||
}
|
||||
#endif
|
||||
} //anonymous namespace
|
||||
|
||||
void
|
||||
MediaEncoder::NotifyQueuedTrackChanges(MediaStreamGraph* aGraph,
|
||||
TrackID aID,
|
||||
TrackRate aTrackRate,
|
||||
TrackTicks aTrackOffset,
|
||||
uint32_t aTrackEvents,
|
||||
const MediaSegment& aQueuedMedia)
|
||||
{
|
||||
// Process the incoming raw track data from MediaStreamGraph, called on the
|
||||
// thread of MediaStreamGraph.
|
||||
if (aQueuedMedia.GetType() == MediaSegment::AUDIO) {
|
||||
mAudioEncoder->NotifyQueuedTrackChanges(aGraph, aID, aTrackRate,
|
||||
aTrackOffset, aTrackEvents,
|
||||
aQueuedMedia);
|
||||
|
||||
} else {
|
||||
// Type video is not supported for now.
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
MediaEncoder::NotifyRemoved(MediaStreamGraph* aGraph)
|
||||
{
|
||||
// In case that MediaEncoder does not receive a TRACK_EVENT_ENDED event.
|
||||
LOG("NotifyRemoved in [MediaEncoder].");
|
||||
mAudioEncoder->NotifyRemoved(aGraph);
|
||||
}
|
||||
|
||||
/* static */
|
||||
already_AddRefed<MediaEncoder>
|
||||
MediaEncoder::CreateEncoder(const nsAString& aMIMEType)
|
||||
{
|
||||
nsAutoPtr<ContainerWriter> writer;
|
||||
nsAutoPtr<AudioTrackEncoder> audioEncoder;
|
||||
nsAutoPtr<VideoTrackEncoder> videoEncoder;
|
||||
nsRefPtr<MediaEncoder> encoder;
|
||||
|
||||
if (aMIMEType.IsEmpty()) {
|
||||
// TODO: Should pick out a default container+codec base on the track
|
||||
// coming from MediaStreamGraph. For now, just default to Ogg+Opus.
|
||||
const_cast<nsAString&>(aMIMEType) = NS_LITERAL_STRING("audio/ogg");
|
||||
}
|
||||
|
||||
bool isAudioOnly = FindInReadable(NS_LITERAL_STRING("audio/"), aMIMEType);
|
||||
#ifdef MOZ_OGG
|
||||
if (IsOggType(aMIMEType)) {
|
||||
writer = new OggWriter();
|
||||
if (!isAudioOnly) {
|
||||
// Initialize the videoEncoder.
|
||||
}
|
||||
#ifdef MOZ_OPUS
|
||||
audioEncoder = new OpusTrackEncoder();
|
||||
#endif
|
||||
}
|
||||
#endif
|
||||
// If the given mime-type is video but fail to create the video encoder.
|
||||
if (!isAudioOnly) {
|
||||
NS_ENSURE_TRUE(videoEncoder, nullptr);
|
||||
}
|
||||
|
||||
// Return null if we fail to create the audio encoder.
|
||||
NS_ENSURE_TRUE(audioEncoder, nullptr);
|
||||
|
||||
encoder = new MediaEncoder(writer.forget(), audioEncoder.forget(),
|
||||
videoEncoder.forget(), aMIMEType);
|
||||
|
||||
|
||||
return encoder.forget();
|
||||
}
|
||||
|
||||
/**
|
||||
* GetEncodedData() runs as a state machine, starting with mState set to
|
||||
* ENCODE_HEADER, the procedure should be as follow:
|
||||
*
|
||||
* While non-stop
|
||||
* If mState is ENCODE_HEADER
|
||||
* Create the header from audio/video encoder
|
||||
* If a header is generated
|
||||
* Insert header data into the container stream of writer
|
||||
* Force copied the final container data from writer
|
||||
* Return the copy of final container data
|
||||
* Else
|
||||
* Set mState to ENCODE_TRACK
|
||||
*
|
||||
* If mState is ENCODE_TRACK
|
||||
* Get encoded track data from audio/video encoder
|
||||
* If a packet of track data is generated
|
||||
* Insert encoded track data into the container stream of writer
|
||||
* If the final container data is copied to aOutput
|
||||
* Return the copy of final container data
|
||||
* If this is the last packet of input stream
|
||||
* Set mState to ENCODE_DONE
|
||||
*
|
||||
* If mState is ENCODE_DONE
|
||||
* Stop the loop
|
||||
*/
|
||||
void
|
||||
MediaEncoder::GetEncodedData(nsTArray<nsTArray<uint8_t> >* aOutputBufs,
|
||||
nsAString& aMIMEType)
|
||||
{
|
||||
MOZ_ASSERT(!NS_IsMainThread());
|
||||
|
||||
aMIMEType = mMIMEType;
|
||||
|
||||
bool reloop = true;
|
||||
while (reloop) {
|
||||
switch (mState) {
|
||||
case ENCODE_HEADER: {
|
||||
nsTArray<uint8_t> buffer;
|
||||
nsresult rv = mAudioEncoder->GetHeader(&buffer);
|
||||
if (NS_FAILED(rv)) {
|
||||
// Encoding might be canceled.
|
||||
mState = ENCODE_DONE;
|
||||
break;
|
||||
}
|
||||
|
||||
if (!buffer.IsEmpty()) {
|
||||
rv = mWriter->WriteEncodedTrack(buffer, 0);
|
||||
if (NS_FAILED(rv)) {
|
||||
LOG("ERROR! Fail to write header to the media container.");
|
||||
mState = ENCODE_DONE;
|
||||
break;
|
||||
}
|
||||
|
||||
rv = mWriter->GetContainerData(aOutputBufs,
|
||||
ContainerWriter::FLUSH_NEEDED);
|
||||
if (NS_SUCCEEDED(rv)) {
|
||||
// Successfully get the copy of final container data from writer.
|
||||
reloop = false;
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
// No more headers, starts to encode tracks.
|
||||
mState = ENCODE_TRACK;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case ENCODE_TRACK: {
|
||||
nsTArray<uint8_t> buffer;
|
||||
int encodedDuration = 0;
|
||||
nsresult rv = mAudioEncoder->GetEncodedTrack(&buffer, encodedDuration);
|
||||
if (NS_FAILED(rv)) {
|
||||
// Encoding might be canceled.
|
||||
LOG("ERROR! Fail to get encoded data from encoder.");
|
||||
mState = ENCODE_DONE;
|
||||
break;
|
||||
}
|
||||
|
||||
rv = mWriter->WriteEncodedTrack(buffer, encodedDuration,
|
||||
mAudioEncoder->IsEncodingComplete() ?
|
||||
ContainerWriter::END_OF_STREAM : 0);
|
||||
if (NS_FAILED(rv)) {
|
||||
LOG("ERROR! Fail to write encoded track to the media container.");
|
||||
mState = ENCODE_DONE;
|
||||
break;
|
||||
}
|
||||
|
||||
rv = mWriter->GetContainerData(aOutputBufs,
|
||||
mAudioEncoder->IsEncodingComplete() ?
|
||||
ContainerWriter::FLUSH_NEEDED : 0);
|
||||
if (NS_SUCCEEDED(rv)) {
|
||||
// Successfully get the copy of final container data from writer.
|
||||
reloop = false;
|
||||
break;
|
||||
}
|
||||
|
||||
mState = (mAudioEncoder->IsEncodingComplete()) ? ENCODE_DONE : ENCODE_TRACK;
|
||||
break;
|
||||
}
|
||||
|
||||
case ENCODE_DONE:
|
||||
LOG("MediaEncoder has been shutdown.");
|
||||
mShutdown = true;
|
||||
reloop = false;
|
||||
break;
|
||||
|
||||
default:
|
||||
MOZ_NOT_REACHED("Invalid encode state");
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,134 @@
|
|||
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-*/
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
* You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#ifndef MediaEncoder_h_
|
||||
#define MediaEncoder_h_
|
||||
|
||||
#include "mozilla/DebugOnly.h"
|
||||
#include "TrackEncoder.h"
|
||||
#include "ContainerWriter.h"
|
||||
#include "MediaStreamGraph.h"
|
||||
|
||||
namespace mozilla {
|
||||
|
||||
/**
|
||||
* MediaEncoder is the framework of encoding module, it controls and manages
|
||||
* procedures between ContainerWriter and TrackEncoder. ContainerWriter packs
|
||||
* the encoded track data with a specific container (e.g. ogg, mp4).
|
||||
* AudioTrackEncoder and VideoTrackEncoder are subclasses of TrackEncoder, and
|
||||
* are responsible for encoding raw data coming from MediaStreamGraph.
|
||||
*
|
||||
* Also, MediaEncoder is a type of MediaStreamListener, it starts to receive raw
|
||||
* segments after itself is added to the source stream. In the mean time,
|
||||
* encoded track data is pulled by its owner periodically on a worker thread. A
|
||||
* reentrant monitor is used to protect the push and pull of resource.
|
||||
*
|
||||
* MediaEncoder is designed to be a passive component, neither it owns nor in
|
||||
* charge of managing threads. However, a monitor is used in function
|
||||
* TrackEncoder::GetEncodedTrack() for the purpose of thread safety (e.g.
|
||||
* between callbacks of MediaStreamListener and others), a call to this function
|
||||
* might block. Therefore, MediaEncoder should not run on threads that forbid
|
||||
* blocking, such as main thread or I/O thread.
|
||||
*
|
||||
* For example, an usage from MediaRecorder of this component would be:
|
||||
* 1) Create an encoder with a valid MIME type.
|
||||
* => encoder = MediaEncoder::CreateEncoder(aMIMEType);
|
||||
* It then generate a ContainerWriter according to the MIME type, and an
|
||||
* AudioTrackEncoder (or a VideoTrackEncoder too) associated with the media
|
||||
* type.
|
||||
*
|
||||
* 2) Dispatch the task GetEncodedData() to a worker thread.
|
||||
*
|
||||
* 3) To start encoding, add this component to its source stream.
|
||||
* => sourceStream->AddListener(encoder);
|
||||
*
|
||||
* 4) To stop encoding, remove this component from its source stream.
|
||||
* => sourceStream->RemoveListener(encoder);
|
||||
*/
|
||||
class MediaEncoder : public MediaStreamListener
|
||||
{
|
||||
public :
|
||||
enum {
|
||||
ENCODE_HEADER,
|
||||
ENCODE_TRACK,
|
||||
ENCODE_DONE,
|
||||
};
|
||||
|
||||
MediaEncoder(ContainerWriter* aWriter,
|
||||
AudioTrackEncoder* aAudioEncoder,
|
||||
VideoTrackEncoder* aVideoEncoder,
|
||||
const nsAString& aMIMEType)
|
||||
: mWriter(aWriter)
|
||||
, mAudioEncoder(aAudioEncoder)
|
||||
, mVideoEncoder(aVideoEncoder)
|
||||
, mMIMEType(aMIMEType)
|
||||
, mState(MediaEncoder::ENCODE_HEADER)
|
||||
, mShutdown(false)
|
||||
{}
|
||||
|
||||
~MediaEncoder() {};
|
||||
|
||||
/**
|
||||
* Notified by the control loop of MediaStreamGraph; aQueueMedia is the raw
|
||||
* track data in form of MediaSegment.
|
||||
*/
|
||||
virtual void NotifyQueuedTrackChanges(MediaStreamGraph* aGraph, TrackID aID,
|
||||
TrackRate aTrackRate,
|
||||
TrackTicks aTrackOffset,
|
||||
uint32_t aTrackEvents,
|
||||
const MediaSegment& aQueuedMedia);
|
||||
|
||||
/**
|
||||
* Notified the stream is being removed.
|
||||
*/
|
||||
virtual void NotifyRemoved(MediaStreamGraph* aGraph);
|
||||
|
||||
/**
|
||||
* Creates an encoder with a given MIME type. Returns null if we are unable
|
||||
* to create the encoder. For now, default aMIMEType to "audio/ogg" and use
|
||||
* Ogg+Opus if it is empty.
|
||||
*/
|
||||
static already_AddRefed<MediaEncoder> CreateEncoder(const nsAString& aMIMEType);
|
||||
|
||||
/**
|
||||
* Encodes the raw track data and returns the final container data. Assuming
|
||||
* it is called on a single worker thread. The buffer of container data is
|
||||
* allocated in ContainerWriter::GetContainerData(), and is appended to
|
||||
* aOutputBufs. aMIMEType is the valid mime-type of this returned container
|
||||
* data.
|
||||
*/
|
||||
void GetEncodedData(nsTArray<nsTArray<uint8_t> >* aOutputBufs,
|
||||
nsAString& aMIMEType);
|
||||
|
||||
/**
|
||||
* Return true if MediaEncoder has been shutdown. Reasons are encoding
|
||||
* complete, encounter an error, or being canceled by its caller.
|
||||
*/
|
||||
bool IsShutdown()
|
||||
{
|
||||
return mShutdown;
|
||||
}
|
||||
|
||||
/**
|
||||
* Cancel the encoding, and wakes up the lock of reentrant monitor in encoder.
|
||||
*/
|
||||
void Cancel()
|
||||
{
|
||||
if (mAudioEncoder) {
|
||||
mAudioEncoder->NotifyCancel();
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
nsAutoPtr<ContainerWriter> mWriter;
|
||||
nsAutoPtr<AudioTrackEncoder> mAudioEncoder;
|
||||
nsAutoPtr<VideoTrackEncoder> mVideoEncoder;
|
||||
nsString mMIMEType;
|
||||
int mState;
|
||||
bool mShutdown;
|
||||
};
|
||||
|
||||
}
|
||||
#endif
|
|
@ -8,10 +8,12 @@ MODULE = 'content'
|
|||
|
||||
EXPORTS += [
|
||||
'ContainerWriter.h',
|
||||
'MediaEncoder.h',
|
||||
'TrackEncoder.h',
|
||||
]
|
||||
|
||||
CPP_SOURCES += [
|
||||
'MediaEncoder.cpp',
|
||||
'TrackEncoder.cpp',
|
||||
]
|
||||
|
||||
|
|
Загрузка…
Ссылка в новой задаче