The VideoConsumer node will be available with R4.5; it's not provided in R4. In addition, there are no video producer nodes in R4; media add-ons for a variety of movie file formats will also be available beginning with R4.5.
The BMediaRoster
class comprises the functionality that applications that
use the Media Kit can access.
An application can only have a single instance of the
BMediaRoster
class,
which is accessed by calling the static member function
BMediaRoster::Roster()
,
which creates the media roster, establishes the
connection to the Media Server, then returns a pointer to the roster.
The creation of the roster object is thread protected, so you can safely
call BMediaRoster::Roster()
from multiple threads without
synchronization, and both threads will safely get the same instance. The
cost of this synchronization is low enough that there's no need to cache
the returned pointer, but it's perfectly safe to do so if you wish:
BMediaRoster *gMediaRoster
; intmain
(void) { status_terr
;BApplication
app
("application/x-vnd.me-myself");gMediaRoster
=BMediaRoster
::Roster
(&err
); if (!gMediaRoster
|| (err
!=B_OK
)) { /* the Media Server appears to be dead -- handle that here */ } /* The Media Server connection is in place -- enjoy! */ return 0; }
Because BMediaRoster
is derived from BLooper
,
you should create your
BApplication
before calling
BMediaRoster::Roster()
,
although the
BApplication
doesn't have to be running yet.
You should never delete the
BMediaRoster
returned to you by
BMediaRoster
.
Also, you can't derive a class from
BMediaRoster
.
If you want to receive notifications from the Media Server when specific
changes occur, such as nodes coming online or going offline, for example,
you can register to receive such notifications by calling
StartWatching()
.
To play a media file from disk, you would follow the following steps:
Create an entry_ref that refers to the file to be played.
Call
SniffRef()
to obtain a
dormant_node_info
reference to the node that is best capable of playing back the media file.
Call
InstantiateDormantNode()
to instantiate the node; this returns a
media_node
that you can use for other
BMediaRoster
calls.
Use
SetRefFor()
to pass the entry_ref
of the file to be played to the node.
Call
GetFreeOutputsFor()
to obtain a
media_output
structure describing an available output on the producer node. This structure contains a
media_source
you can pass to
Connect()
as the source of the media data.
The
media_output
structure returned by
GetFreeOutputsFor()
contains a
media_format
field that indicates the general type of media data
contained by the media file; if you don't already know what type of
data you're playing back (audio or video or whatever), this will let
you determine what you'll be playing.
You can use this value to determine what type of destination is
needed to output the data (for example, B_MEDIA_RAW_AUDIO
would go to
an audio output, and B_MEDIA_RAW_VIDEO
would go to a video output).
Call
GetAudioMixer()
to get a media_node
for the default audio mixer (if the media data is audio), or
GetVideoOutput()
to get a media_node
for the default video output (if the media data is video).
Use
GetFreeInputsFor()
to obtain a media_input
structure describing
an available input on the consumer node (the audio mixer or video
output); this structure contains a
media_destination
you can pass to
Connect()
.
Call Connect()
to connect the source to the destination.
Next, you need to set the time source for the media file's node. Normally you'll set this to the preferred time source. The audio mixer or video output is already slaved to an appropriate time source.
Finally, you can use
StartNode()
to start the producer, consumer, and
time source. The media file should begin playing back.
Let's look at actual sample code that does this. This example is
particular to playing movie files; in particular, it assumes that the
video is encoded (B_MEDIA_ENCODED_VIDEO
) and that the audio is in a raw
audio format (B_MEDIA_RAW_AUDIO
). However, it demonstrates the principles
of playing both encoded and raw media formats, and you can easily
extrapolate from it any type of playback you need. First it's necessary
to identify an appropriate node to handle the file, and to instantiate
the node and configure it for the file we want to play:
bigtime_tduration
; media_nodetimeSourceNode
; media_nodemediaFileNode
; media_outputfileNodeOutput
; int32fileOutputCount
; media_outputfileAudioOutput
; int32fileAudioCount
; media_nodecodecNode
; media_outputcodecOutput
; media_inputcodecInput
; media_nodevideoNode
; media_inputvideoInput
; int32videoInputCount
; media_nodeaudioNode
; media_inputaudioInput
; int32audioInputCount
; dormant_node_infonodeInfo
; status_terr
;playingFlag
=false
;roster
=BMediaRoster
::Roster
();initStatus
=roster
->SniffRef
(*ref
, 0, &nodeInfo
); if (initStatus
) { return; }initStatus
=roster
->InstantiateDormantNode
(nodeInfo
, &mediaFileNode
); if (initStatus
) { return; }roster
->SetRefFor
(mediaFileNode
, *ref
,false
, &duration
); if ((err
=Setup
()) !=B_OK
) {printf
("Error %08lX in Setup()\n",err
); } else {Start
(); }
This code begins by obtaining a pointer to the media roster. It then
calls SniffRef()
to get a
dormant_node_info
structure describing the
best-suited node for reading the media data from the file specified by
ref
.
Once a dormant_node_info structure has been filled out, the
InstantiateDormantNode()
function is called to instantiate a node to
handle the file. A dormant node is a node whose code resides in an
add-on, instead of within the application itself. The
nodeInfo
structure
is passed into the function, and on return, the mediaFileNode
has been
set up for the appropriate node.
Since the
media_node
mediaFileNode
is a file handling node, the
SetRefFor()
function is then called to tell the newly-instantiated file
handler node what file it should handle. The inputs here are:
mediaFileNode
is the node whose file reference is to be set.
ref
is the file that the node should reference.
false
indicates that the file must already exist. If this flag were
true
, and the file indicated by ref
were nonexistent, the node would
create a new file. Since we're playing a file, we don't want to do that.
duration
is a bigtime_t variable
that will receive the duration of the media file, in microseconds.
Once this has been accomplished, it's time to instantiate the other nodes
needed to perform the media playback. Note that your code should check
the error results from each of these calls and only proceed if
B_OK
is returned.
The Setup()
and Start()
functions used in the example above are given below.
Setup()
actually sets up the connections and instantiates the
various other nodes (such as codecs and output nodes) required to play
back the media data. Let's take a look at
Setup()
next:
status_tMediaPlayer
::Setup
(void) { status_terr
; media_formattryFormat
; dormant_node_infonodeInfo
; int32nodeCount
;err
=roster
->GetAudioMixer
(&audioNode
);err
=roster
->GetVideoOutput
(&videoNode
);
First, GetAudioMixer()
and GetVideoOutput()
are called to obtain an audio
mixer node and a video output node. The nodes returned by this function
are based on the user's preferences in the Audio and Video preference
applications. By default, video is output to a simple video output
consumer that creates a window to contain the video display.
The VideoConsumer node will be available with R4.5; it's not provided in R4. In addition, there are no video producer nodes in R4; media add-ons for a variety of movie file formats will also be available beginning with R4.5.
err
=roster
->GetTimeSource
(&timeSourceNode
);b_timesource
=roster
->MakeTimeSourceFor
(timeSourceNode
);
This code obtains a
media_node
for the preferred time source, and then creates a
BTimeSource
object that refers to the same node; we'll need to be able to make some
BTimeSource
calls to obtain some specific timing information later.
A time source is a node that can be used to synchronize other nodes. By default, nodes are slaved to the system time source, which is the computer's internal clock. However, this time source, while very precise, isn't good for synchronizing media data, since its concept of time has nothing to do with actual media being performed. For this reason, you typically will want to change nodes' time sources to the preferred time source.
You can think of a media node (represented by the media_node structure) as a component in a home theater system you might have at home. It has inputs for audio and video (possibly multiple inputs for each), and outputs to pass that audio and video along to other components in the system. To use the component, you have to connect wires from the outputs of some other components into the component's inputs, and the outputs into the inputs of other components.
The Media Kit works the same way. We need to locate audio outputs from
the mediaFileNode
and find corresponding audio
inputs on the audioNode
.
This is analogous to choosing an audio output from your new DVD player
and matching it to an audio input jack on your stereo receiver. Since you
can't use ports that are already in use, we call
GetFreeOutputsFor()
to find free output ports on the mediaFileNode
, and
GetFreeInputsFor()
to locate free input ports on the audioNode
.
err
=roster
->GetFreeOutputsFor
(mediaFileNode
, &fileAudioOutput
, 1, &fileAudioCount
,B_MEDIA_RAW_AUDIO
);err
=roster
->GetFreeInputsFor
(audioNode
, &audioInput
,fileAudioCount
, &audioInputCount
,B_MEDIA_RAW_AUDIO
);
We only want a single audio connection between the two nodes (a
single connection can carry stereo sound), and the connection is of type
B_MEDIA_RAW_AUDIO
. On return,
fileAudioOutput
and audioInput
describe the output from the mediaFlieNode
and the input
into the audioNode
that will eventually be connected to
play the movie's sound.
We likewise have to find a video output from the
mediaFileNode
and an input into the
videoNode
. In this case, though, we expect the video
output from the mediaFileNode
to be encoded, and the
videoNode
will want to receive raw, uncompressed video.
We'll work that out in a minute; for now, let's just find the two
ports:
err
=roster
->GetFreeOutputsFor
(mediaFileNode
, &fileNodeOutput
, 1, &fileOutputCount
,B_MEDIA_ENCODED_VIDEO
)err
=roster
->GetFreeInputsFor
(videoNode
, &videoInput
,fileOutputCount
, &videoInputCount
,B_MEDIA_RAW_VIDEO
);
The problem we have now is that the mediaFileNode
is outputting video
that's encoded somehow (like in Cinepak format, for instance). The
videoNode
, on the other hand, wants to display raw video. Another node
must be placed between these to decode the video (much like having an
adapter to convert PAL video into NTSC, for example). This node will be
the codec that handles decompressing the video into raw form.
We need to locate a codec node that can handle the video format being
output by the mediaFileNode
. This is accomplished like this:
nodeCount
= 1;err
=roster
->GetDormantNodes
(&nodeInfo
, &nodeCount
, &fileNodeOutput
.format
); if (!nodeCount
) { return -1; }
This call to
GetDormantNodes()
looks for a dormant node that can handle the media format specified by the
mediaFileNode
's output
media_format
structure. Information about the node is returned in nodeInfo
.
nodeCount
indicates the number of matching nodes that
were found. If it's zero, an error is returned.
Note that in real life you should ask for several nodes, and search through them, looking at the formats until you find one that best meets your needs.
Then we use
InstantiateDormantNode()
to instantiate the codec node, and
locate inputs into the node (that accept encoded video) and outputs from
the node (that output raw video):
err
=roster
->InstantiateDormantNode
(nodeInfo
, &codecNode
);err
=roster
->GetFreeInputsFor
(codecNode
, &codecInput
, 1, &nodeCount
,B_MEDIA_ENCODED_VIDEO
);err
=roster
->GetFreeOutputsFor
(codecNode
, &codecOutput
, 1, &nodeCount
,B_MEDIA_RAW_VIDEO
);
Now we're ready to start connecting these nodes together. If we were setting up a home theater system, right about now we'd be getting rug burns on our knees and skinned knuckles on our hands, trying to reach behind the entertainment center to run wires. The Media Kit is way easier than that, and doesn't involve salespeople telling you to get expensive gold-plated cables.
We begin by connecting the file node's video output to the codec's input:
tryFormat
=fileNodeOutput
.format
;err
=roster
->Connect
(fileNodeOutput
.source
,codecInput
.destination
, &tryFormat
, &fileNodeOutput
, &codecInput
);
tryFormat
indicates the format of the encoded video that will be output
by the mediaFileNode
.
Connect()
,
in essense, runs a wire between the
output from the media node's video output (fileNodeOutput
) to the codec
node's input.
You may wonder what's up with the fileNodeOutput
.source
and
codecInput
.destination
structures. These
media_source and
media_destination
structures are simplified descriptors of the two ends
of the connection. They contain only the data absolutely needed for the
Media Kit to establish the connection. This saves some time when issuing
the
Connect()
call (and time is money, especially in the media business).
Next it's necessary to connect the codec to the video output node. This
begins by setting up tryFormat
to describe raw video of the same width
and height as the encoded video being fed into the codec, then calling
Connect()
to establish the connection:
tryFormat
.type
=B_MEDIA_RAW_VIDEO
;tryFormat
.u
.raw_video
= media_raw_video_format::wildcard
;tryFormat
.u
.raw_video
.display
.line_width
=codecInput
.format
.u
.encoded_video
.output
.display
.line_width
;tryFormat
.u
.raw_video
.display
.line_count
=codecInput
.format
.u
.encoded_video
.output
.display
.line_count
;err
=roster
->Connect
(codecOutput
.source
,videoInput
.destination
, &tryFormat
, &codecOutput
, &videoInput
);
Now we connect the audio from the media file to the audio mixer node. We just copy the media_format from the file's audio output, since both ends of the connection should exactly match.
tryFormat
=fileAudioOutput
.format
;err
=roster
->Connect
(fileAudioOutput
.source
,audioInput
.destination
, &tryFormat
, &fileAudioOutput
, &audioInput
);
The last step of configuring the connections is to ensure that all the nodes are slaved to the preferred time source. This will keep them synchronized with the preferred time source (and by association, with each other):
err
=roster
->SetTimeSourceFor
(mediaFileNode
.node
,timeSourceNode
.node
);err
=roster
->SetTimeSourceFor
(videoNode
.node
,timeSourceNode
.node
);err
=roster
->SetTimeSourceFor
(codecOutput
.node
.node
,timeSourceNode
.node
);err
=roster
->SetTimeSourceFor
(audioNode
.node
,timeSourceNode
.node
); returnB_OK
; }
Finally, we return B_OK
to the caller. Note that this code should be
enhanced to check the results of each
BMediaRoster
call, and to return the result code if it's not B_OK
.
This has been left out of this example for brevity.
The Start()
function actually starts the movie playback. Starting
playback involves starting, one at a time, all the nodes involved in
playing back the audio. This includes the audio mixer (audioNode
), the
media file's node (mediaFileNode
), the codec, and the video node.
status_tMediaPlayer
::Start
(void) { status_terr
;err
=roster
->GetStartLatencyFor
(timeSourceNode
, &startTime
);startTime
+=b_timesource
->PerformanceTimeFor
(BTimeSource
::RealTime
() + 1000000 / 50);err
=roster
->StartNode
(mediaFileNode
,startTime
);err
=roster
->StartNode
(codecNode
,startTime
);err
=roster
->StartNode
(videoNode
,startTime
); returnB_OK
; }
Because there's lag time between starting each of these nodes, we pick a time a few moments in the future for playback to begin, and schedule each node to start playing at that time. So we begin by computing that time in the future.
The
BTimeSource::RealTime()
static member function is called to obtain
the current real system time. We add a fiftieth of a second to that time,
and convert it into performance time units. This is the time at which the
performance of the movie will begin (basically a fiftieth of a second
from "now"). This value is saved in startTime
. These are added to the
value returned by
GetStartLatencyFor()
,
which returns the time required
to actually start the time source and all the nodes slaved to it.
Then we simply call
BMediaRoster::StartNode()
for each node, specifying
startTime
as the performance time at which playback should begin.
Again, error handling should be added to actually return the error code from these functions.
Stopping playback of the movie is even simpler:
err
=roster
->StopNode
(mediaFileNode
, 0,true
);err
=roster
->StopNode
(codecNode
, 0,true
);err
=roster
->StopNode
(videoNode
, 0,true
);
This tells the media file, video codec, and video output nodes to stop
immediately. If we wanted them to stop together at some time in the
future, we could compute an appropriate performance time and pass that
instead of 0. In this case, we would need to specify false
for the last
argument; when this value is true
,
StopNode()
stops the node immediately.
We could use this ability to schedule all three nodes to stop at the same
time, so that video and audio playback would halt simultaneously.
Note that we don't stop the audio mixer node. You should never stop the mixer node, because other applications are probably using it.
Once you're done playing the movie, and have stopped playback, you should disconnect the nodes from each other:
err
=roster
->Disconnect
(mediaFileNode
.node
,fileNodeOutput
.source
,codecNode
.node
,codecInput
.destination
);err
=roster
->Disconnect
(codecNode
.node
,codecOutput
.source
,videoNode
.node
,videoInput
.destination
);err
=roster
->Disconnect
(mediaFileNode
.node
,fileAudioOutput
.source
,audioNode
.node
,audioInput
.destination
);
This will close out the connections between the media file node and the video codec, the codec and the video output, and between the file node and the audio mixer. You should always stop playback before disconnecting; although nodes aren't allowed to crash if you disconnect them while running, their behavior isn't specified, and may not be what you expect.
Once the connections are severed, you should release any dormant nodes
you instantiated. This includes not only nodes instantiated using
InstantiateDormantNode()
,
but also default nodes (those obtained using functions like
GetAudioInput()
and
GetVideoOutput)
,
for example):
roster
->ReleaseNode
(codecNode
);roster
->ReleaseNode
(mediaFileNode
);roster
->ReleaseNode
(videoNode
);roster
->ReleaseNode
(audioNode
);
If you want to play audio, you may find it much easier to use the
BSound
and
BSoundPlayer
classes to do so. As of R4.5, there are no
Be-provided nodes for producing audio from a disk file.
There isn't a Media Kit function that can directly tell you whether or not the media has reached the end of the data during playback. However, the following easy-to-implement code can do the job for you:
bigtime_tcurrentTime
; boolisPlaying
=true
;currentTime
=b_timesource
->PerformanceTimeFor
(BTimeSource
::RealTime
()); if (currentTime
>=startTime
+duration
) {isPlaying
=false
; }
This works by obtaining the time source's performance time and comparing it to the time at which playback of the movie was begun plus the movie's duration (both of which were saved when we initially set up and began playback of the movie, as seen in the code in the previous section above).
If the current performance time is equal to or greater than the sum of
the starting time and the movie's duration, then playback is finished,
and we set isPlaying
to false
;
otherwise, this value remains true
.
You can issue
BMediaRoster
function calls from within your own node, however, as a general rule, you
shouldn't call BMediaRoster
functions
from within your control thread, or while the control thread is blocked.
Many BMediaRoster
functions use synchronous turnarounds, and will
deadlock in this situation. You should assume, for safety's sake, that
all BMediaRoster
functions will deadlock if used in these cases.
For example, if you have an application that's playing video into a
window, and you call
StopNode()
from the window's
MessageReceived()
function, a deadlock would occur if the video player node blocks waiting
on the window to be unlocked, and the
StopNode()
function is keeping the
window locked while it waits for the video producer node, which is
blocked waiting on the consumer node, and so forth. Deadlock results, and
that's a bad thing.
Instead, you should consider creating a seperate
BLooper
that manages
your nodes. Future versions of the Media Kit will provide convenience
classes to do some of this for you.