798 lines
61 KiB
HTML
798 lines
61 KiB
HTML
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
|
||
<html xmlns="http://www.w3.org/1999/xhtml"><head><meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /><title>Be Newsletters - Volume 4: 1999</title><link rel="stylesheet" href="be_newsletter.css" type="text/css" media="all" /><link rel="shortcut icon" type="image/vnd.microsoft.icon" href="./images/favicon.ico" /><!--[if IE]>
|
||
<link rel="stylesheet" type="text/css" href="be_newsletter_ie.css" />
|
||
<![endif]--><meta name="generator" content="DocBook XSL Stylesheets V1.73.2" /><link rel="start" href="index.html" title="Be Newsletters" /><link rel="up" href="volume4.html" title="Volume 4: 1999" /><link rel="prev" href="Issue4-5.html" title="Issue 4-5, February 3, 1999" /><link rel="next" href="Issue4-7.html" title="Issue 4-7, February 17, 1999" /></head><body><div id="header"><div id="headerT"><div id="headerTL"><a accesskey="p" href="Issue4-5.html" title="Issue 4-5, February 3, 1999"><img src="./images/navigation/prev.png" alt="Prev" /></a> <a accesskey="u" href="volume4.html" title="Volume 4: 1999"><img src="./images/navigation/up.png" alt="Up" /></a> <a accesskey="n" href="Issue4-7.html" title="Issue 4-7, February 17, 1999"><img src="./images/navigation/next.png" alt="Next" /></a></div><div id="headerTR"><div id="navigpeople"><a href="http://www.haiku-os.org"><img src="./images/People_24.png" alt="haiku-os.org" title="Visit The Haiku Website" /></a></div><div class="navighome" title="Home"><a accesskey="h" href="index.html"><img src="./images/navigation/home.png" alt="Home" /></a></div><div class="navigboxed" id="naviglang" title="English">en</div></div><div id="headerTC">Be Newsletters - Volume 4: 1999</div></div><div id="headerB">Prev: <a href="Issue4-5.html">Issue 4-5, February 3, 1999</a> Up: <a href="volume4.html">Volume 4: 1999</a> Next: <a href="Issue4-7.html">Issue 4-7, February 17, 1999</a></div><hr /></div><div class="article"><div xmlns="" xmlns:d="http://docbook.org/ns/docbook" class="titlepage"><div><div xmlns:d="http://docbook.org/ns/docbook"><h2 xmlns="http://www.w3.org/1999/xhtml" class="title"><a id="Issue4-6"></a>Issue 4-6, February 10, 1999</h2></div></div></div><div class="sect1"><div xmlns="" xmlns:d="http://docbook.org/ns/docbook" class="titlepage"><div><div xmlns:d="http://docbook.org/ns/docbook"><h2 xmlns="http://www.w3.org/1999/xhtml" class="title"><a id="Engineering4-6"></a>Be Engineering Insights: Abusing Multithreading</h2></div><div xmlns:d="http://docbook.org/ns/docbook"><span xmlns="http://www.w3.org/1999/xhtml" class="author">By <span class="firstname">Cyril</span> <span class="surname">Meurillon</span></span></div></div></div><p>
|
||
We've been telling you since the beginning that threads are neat and
|
||
wonderful. A number of Newsletter articles have been written on how to
|
||
increase parallelism in your applications by creating more threads and
|
||
synchronizing them. Here are a few of those articles:
|
||
</p><p>
|
||
<a class="xref" href="Issue3-32.html#Engineering3-32" title="Be Engineering Insights: Fun with Threads, Part 1">Be Engineering Insights: Fun with Threads, Part 1</a><br />
|
||
<a class="xref" href="Issue3-33.html#Engineering3-33" title="Be Engineering Insights: Fun with Threads, Part 2">Be Engineering Insights: Fun with Threads, Part 2</a><br />
|
||
<a class="xref" href="Issue2-13.html#Engineering2-13" title="Be Engineering Insights: Fun with Semaphores">Be Engineering Insights: Fun with Semaphores</a><br />
|
||
<a class="xref" href="Issue1-4.html#Engineering1-4" title="Be Engineering Insights: Summer Vacations and Semaphores">Be Engineering Insights: Summer Vacations and Semaphores</a>
|
||
</p><p>
|
||
All this is good, but it helped create the impression that programmers
|
||
who spawn more threads are smarter than others. This perhaps led to some
|
||
abuse and excess, with what appears to be a contest between some
|
||
programmers for who will spawn the most threads. This doesn't really come
|
||
as a surprise. Since multithreading is a relatively new programming
|
||
model, some abuse is to be expected.
|
||
</p><p>
|
||
Abuse—or excessive use—of multithreading can cause a number of
|
||
problems. First, threads are not free. It's true that they are
|
||
lightweight. A BeOS thread is not as heavyweight as a typical UNIX
|
||
process—for example, the address space and file descriptors are shared
|
||
between teams. But they do use up precious system resources: memory,
|
||
semaphores, etc.
|
||
</p><p>
|
||
For a start, each thread has a stack, which has to be allocated and
|
||
consumes memory. How much memory? It depends on the thread, as stack
|
||
pages are allocated on demand. But worse is the fact that the kernel
|
||
stack—the part of the stack which is used when the thread runs in the
|
||
kernel—has to be preallocated and locked down in memory—it can't be
|
||
paged out to disk. This is about 32K of memory per thread that you can
|
||
kiss goodbye. In addition, there are kernel structures used to keep the
|
||
thread state, some of them stored in locked memory as well. As a result,
|
||
20 threads eat up approximately 1M of memory. That's a lot.
|
||
</p><p>
|
||
Perhaps you don't care: you are writing an application targeted to
|
||
high-end users with plenty of memory. And you're telling me that memory
|
||
is cheap these days anyway. I could argue that this approach tends to
|
||
make systems fatter and slower. But I have something more important to
|
||
warn you about. By creating more threads and having to synchronize them,
|
||
you greatly increase the risks of deadlocks. That risk does not increase
|
||
linearly with the number of threads, but with the number of possible
|
||
interactions between threads—which grows much faster.
|
||
</p><p>
|
||
Since I'm discussing interactions between threads, it might be useful to
|
||
distinguish between different kinds of multithreading.
|
||
</p><div class="sect2"><div xmlns="" xmlns:d="http://docbook.org/ns/docbook" class="titlepage"><div><div xmlns:d="http://docbook.org/ns/docbook"><h3 xmlns="http://www.w3.org/1999/xhtml" class="title"><a id="id536325"></a>Regular Multithreading</h3></div></div></div><p>
|
||
The threads are executing the same code on different data, and there is
|
||
very little interaction between them. Typically, applications dealing
|
||
with computations that can be easily parallelized, as on images, resort
|
||
to this technique. Servers can also fire a thread for every client
|
||
request, if the request is complex enough. For this type of
|
||
multithreading, synchronization between the different threads is usually
|
||
quite basic: the control thread fires all the computing threads and waits
|
||
for them to complete.
|
||
</p></div><div class="sect2"><div xmlns="" xmlns:d="http://docbook.org/ns/docbook" class="titlepage"><div><div xmlns:d="http://docbook.org/ns/docbook"><h3 xmlns="http://www.w3.org/1999/xhtml" class="title"><a id="id536336"></a>Irregular Multithreading</h3></div></div></div><p>
|
||
The threads interact in a complex manner. Each thread can be seen as an
|
||
active agent that processes data and sends messages to other agents.
|
||
Media Server nodes are an example of such an organization.
|
||
Synchronization is usually complex. It can be of the producer-consumer
|
||
type, using ports. Or it can be customized, based on basic tools like
|
||
semaphores or shared memory.
|
||
</p><p>
|
||
It is clearly not a universal classification. Just a simple way to look
|
||
at multithreading from the standpoint of interaction complexity.
|
||
</p><p>
|
||
With regular multithreading, adding threads does not add to the overall
|
||
complexity of the application, because the additional threads don't
|
||
interact between themselves. On the other hand, adding threads in the
|
||
context of irregular multithreading increases complexity because the
|
||
additional threads do interact between themselves.
|
||
</p><p>
|
||
My warning about the increased risk of deadlock clearly only concerns
|
||
applications that use irregular multithreading. The risk of deadlock is
|
||
high, and quickly gets beyond anybody's comprehension as the number of
|
||
threads involved becomes larger than just a few. These deadlocks are
|
||
usually non-trivial, and it might prove useful to review some of the
|
||
classic scenarios:
|
||
</p><div class="sect3"><div xmlns="" xmlns:d="http://docbook.org/ns/docbook" class="titlepage"><div><div xmlns:d="http://docbook.org/ns/docbook"><h4 xmlns="http://www.w3.org/1999/xhtml" class="title"><a id="id536377"></a>With nested semaphores:</h4></div></div></div><p>
|
||
In an architecture where semaphores are nested, it's critical that the
|
||
semaphores are always acquired in the same order by all the threads. If
|
||
thread A acquires semaphore a and then semaphore b, and thread B acquires
|
||
b and then a, a deadlock occurs. This gets increasingly complicated as
|
||
the number of nested semaphores and threads increases.
|
||
</p></div><div class="sect3"><div xmlns="" xmlns:d="http://docbook.org/ns/docbook" class="titlepage"><div><div xmlns:d="http://docbook.org/ns/docbook"><h4 xmlns="http://www.w3.org/1999/xhtml" class="title"><a id="id536394"></a>With producer-consumer synchronization:</h4></div></div></div><p>
|
||
It's easy to run into a circular dependence. The loop can involve two or
|
||
more threads. With two threads, A sends a request to B that sends itself
|
||
a request to A. If the requests are synchronous, the deadlock appears
|
||
right away. In the asynchronous case, the message queue A is reading from
|
||
has to be full for the deadlock to be triggered. This makes it hard to
|
||
localize these bugs, and that can remain hidden for a long time. A
|
||
standard but terrible work-around for these bugs is to increase the
|
||
message buffer size... In addition, circular dependence that involves
|
||
loops with more than two threads is difficult to find.
|
||
</p></div><div class="sect3"><div xmlns="" xmlns:d="http://docbook.org/ns/docbook" class="titlepage"><div><div xmlns:d="http://docbook.org/ns/docbook"><h4 xmlns="http://www.w3.org/1999/xhtml" class="title"><a id="id536406"></a>With reader/writer synchronization:</h4></div></div></div><p>
|
||
A simple implementation of reader/writer synchronization involves a
|
||
counted semaphore initialized with a count of N. Readers acquire the
|
||
semaphore with a count of one, writers with a count of N. This
|
||
implementation allows up to N concurrent readers. But what if a reader
|
||
wants to become a writer? You might think that it should acquire N-1, but
|
||
this leads to a deadlock if there is another writer in the wait queue of
|
||
the semaphore. The correct technique is to release as a reader and
|
||
reacquire as a writer.
|
||
</p></div><div class="sect3"><div xmlns="" xmlns:d="http://docbook.org/ns/docbook" class="titlepage"><div><div xmlns:d="http://docbook.org/ns/docbook"><h4 xmlns="http://www.w3.org/1999/xhtml" class="title"><a id="id536417"></a>With benaphores</h4></div></div></div><p>
|
||
(see <a class="xref" href="Issue1-26.html#Engineering1-26" title="Be Engineering Insights: Benaphores">Be Engineering Insights: Benaphores</a>)
|
||
</p><p>
|
||
Benaphores were invented by Dr. Benoît Schillings in the infancy of BeOS.
|
||
They look like semaphores, taste like semaphores, and they're faster. But
|
||
they lost some features offered by kernel semaphores. This means you
|
||
can't always take an application and replace its semaphores by benaphores.
|
||
</p><p>
|
||
Specifically:
|
||
</p><p>
|
||
Benaphores require all the threads using the benaphore to live in the
|
||
same address space, as they rely on shared memory. Semaphores are
|
||
systemwide.
|
||
</p><p>
|
||
It's illegal to acquire a deleted benaphore. Acquiring a deleted
|
||
semaphore guarantees to return <code class="constant">B_BAD_SEM_ID</code>. Semaphore deletion can hence
|
||
be used to synchronize two threads. Not benaphore deletion.
|
||
</p><p>
|
||
Benaphores cannot be acquired with a count greater than 1 in the general
|
||
case. For example, implementing the previous reader/writer
|
||
synchronization using benaphores will lead to deadlocks in the case there
|
||
is more than one thread asking for write access. Think of the following
|
||
scenario:
|
||
</p><ul class="itemizedlist"><li><p>
|
||
The benaphore is initialized. Initial benaphore count: N. Its
|
||
associated semaphore has a count of 0.
|
||
</p></li><li><p>
|
||
Thread A gets read access. The benaphore count decrements to N-1.
|
||
</p></li><li><p>
|
||
Thread B asks for write access. The benaphore count decrements to -1.
|
||
But it is preempted before it acquires the semaphore with a count of 1.
|
||
</p></li><li><p>
|
||
Thread C asks for write access. The benaphore count decrements to
|
||
-1-N and C waits on the associated semaphore with a count of N.
|
||
</p></li><li><p>
|
||
Thread B finally waits on the semaphore with a count of 1.
|
||
</p></li><li><p>
|
||
Thread A releases its read access. It should unblock B, waiting for
|
||
its write access. The benaphore count increments to -N and the
|
||
semaphore is released with a count of 1. Unfortunately, C "passed" B in
|
||
the semaphore queue, and C won't be released because it's waiting with
|
||
a count of N.
|
||
</p></li></ul></div></div><p>
|
||
I remember my initial experience of C++, and I can't help thinking of
|
||
developers confronting multithreading for the first time. My first
|
||
reaction to C++ was enthusiasm. I was very excited about seemingly neat
|
||
features like operator overloading and multiple inheritance. I used it
|
||
and abused it. What had to happen happened: I got trapped a number of
|
||
times by subtleties I did not grasp in the first place, many of them
|
||
being undocumented or compiler dependent. I almost became disillusioned
|
||
from getting burned so many times. But eventually, I got a good sense of
|
||
balance, knowing where to stop the house of cards I was building.
|
||
</p><p>
|
||
It's the same with multithreading: I believe all developers go through
|
||
the same phases of excitement, disillusion and eventually wisdom. I can
|
||
only recommend the greatest precautions when designing multithreaded
|
||
applications. It's tempting to push multithreading to its limits, using a
|
||
lot of threads that interact in complex ways. But it's always better to
|
||
start with an architecture you fully comprehend.
|
||
</p></div><hr class="pagebreak" /><div class="sect1"><div xmlns="" xmlns:d="http://docbook.org/ns/docbook" class="titlepage"><div><div xmlns:d="http://docbook.org/ns/docbook"><h2 xmlns="http://www.w3.org/1999/xhtml" class="title"><a id="Engineering4-6-2"></a>Be Engineering Insights: Using Archived Interface Objects</h2></div><div xmlns:d="http://docbook.org/ns/docbook"><span xmlns="http://www.w3.org/1999/xhtml" class="author">By <span class="firstname">Jean-Baptiste</span> <span class="surname">Queru</span></span></div></div></div><p>
|
||
Many developers ask how to archive interface objects and reuse them
|
||
later. There are several reasons why you'd want to do that—here are
|
||
the main ones:
|
||
</p><ul class="itemizedlist"><li><p>
|
||
It makes sharing the work among a team much easier. One person can
|
||
design the UI, while another actually writes the code for it. It allows
|
||
non-programmers to design user interfaces, and programmers don't have to
|
||
change their code each time the user interface design changes.
|
||
</p></li><li><p>
|
||
It doesn't hard-code sizes, colors, layouts, texts, and most other
|
||
visible interface parameters, making it easier to create different
|
||
interfaces for the same app (the most striking example is
|
||
internationalization, but it's not the only one).
|
||
</p></li><li><p>
|
||
It makes it possible to write very little code in the app itself to
|
||
create the interface objects and put them on screen.
|
||
</p></li></ul><p>
|
||
That's enough marketing talk—this is "Engineering Insights" after all.
|
||
Let's see what has to be engineered—and here's the good news --
|
||
there's nothing you have to do, or, more accurately, very little.
|
||
</p><p>
|
||
Take a look at this example:
|
||
</p><p>
|
||
ftp://ftp.be.com/pub/samples/interface_kit/archint.zip
|
||
</p><p>
|
||
It comes with a makefile. Compile it with <code class="command">make</code>, or use an alternate
|
||
resource with <code class="command">make alt</code>. Believe or not, both versions use the exact
|
||
same code (with no cheating by simply checking a resource flag and
|
||
changing the whole behavior of the app depending on this flag).
|
||
</p><p>
|
||
Now let's see the magic code, in <code class="filename">Application.cpp</code>:
|
||
</p><p>
|
||
The first part loads a <code class="classname">BMessage</code> from a resource (no great magic here):
|
||
</p><pre class="programlisting cpp">
|
||
<span class="type"><code class="classname">BMessage</code>*</span> <code class="function">ReadMessageFromResource</code>(<span class="type">int32</span> <code class="parameter">id</code>) {
|
||
<span class="type">app_info</span> <code class="varname">ai</code>;
|
||
<code class="classname">BFile</code> <code class="varname">f</code>;
|
||
<code class="classname">BResources</code> <code class="varname">r</code>;
|
||
<span class="type">size_t</span> <code class="varname">res_size</code>;
|
||
<span class="type">const void*</span> <code class="varname">res_addr</code>;
|
||
<code class="classname">BMessage</code>* msg=new <code class="classname">BMessage</code>;
|
||
if ((<code class="varname">be_app</code>-><code class="methodname">GetAppInfo</code>(&<code class="varname">ai</code>)!=<code class="constant">B_OK</code>)
|
||
||(<code class="varname">f</code>.<code class="methodname">SetTo</code>(&<code class="varname">ai</code>.<code class="varname">ref</code>,<code class="constant">B_READ_ONLY</code>)!=<code class="constant">B_OK</code>)
|
||
||(<code class="varname">r</code>.<code class="methodname">SetTo</code>(&<code class="varname">f</code>)!=<code class="constant">B_OK</code>)
|
||
||((<code class="varname">res_addr</code>=<code class="varname">r</code>.<code class="methodname">LoadResource</code>(<code class="constant">B_MESSAGE_TYPE</code>,<code class="parameter">id</code>,&<code class="varname">res_size</code>))==<code class="constant">NULL</code>)
|
||
||(<code class="varname">msg</code>-><code class="methodname">Unflatten</code>((<span class="type">const char*</span>)<code class="varname">res_addr</code>)!=<code class="constant">B_OK</code>)) {
|
||
delete <code class="varname">msg</code>;
|
||
return <code class="constant">NULL</code>;
|
||
}
|
||
return <code class="varname">msg</code>;
|
||
}
|
||
</pre><p>
|
||
The second part creates the window from the <code class="classname">BMessage</code>; this is where the
|
||
magic lies:
|
||
</p><pre class="programlisting cpp">
|
||
<code class="classname">AWindow</code>::<code class="methodname">AWindow</code>(<code class="classname">BMessage</code>* <code class="parameter">m</code>):<code class="classname">BWindow</code>(<code class="parameter">m</code>) {
|
||
<code class="function">atomic_add</code>(&<code class="varname">numwindows</code>,1);
|
||
<code class="varname">numclicks</code> = <code class="parameter">m</code>-><code class="methodname">FindInt32</code>("clicks");
|
||
<code class="methodname">Show</code>();
|
||
}
|
||
</pre><p>
|
||
As you can see, there's nothing difficult here. You just call the base
|
||
class constructor and extract any extra data you might have stored in the
|
||
<code class="classname">BMessage</code>. You'll notice that I didn't define any
|
||
<code class="methodname">Instantiate()</code> function for my class, because I
|
||
knew which class was stored in the <code class="classname">BMessage</code>. If
|
||
you're doing something more complex (such as a multiple window interface),
|
||
you'll want to implement <code class="methodname">Instantiate()</code> so that you
|
||
can take a bunch of archived objects and feed them to
|
||
<code class="function">instantiate_object()</code>, which will do the job for you.
|
||
See the <code class="classname">BArchivable</code> chapter in the Be Book for more
|
||
details.
|
||
</p><p>
|
||
This last part shows why custom <code class="classname">BView</code>s are not needed:
|
||
</p><pre class="programlisting cpp">
|
||
<span class="type">void</span> <code class="classname">AWindow</code>::<code class="classname">MessageReceived</code>(<code class="classname">BMessage</code>* <code class="parameter">m</code>) {
|
||
switch(<code class="parameter">m</code>-><code class="varname">what</code>) {
|
||
case 'colr' : {
|
||
<span class="comment">// removed for clarity</span>
|
||
break;
|
||
}
|
||
case 'dump' : {
|
||
<code class="methodname">DumpArchive</code>();
|
||
break;
|
||
}
|
||
case 'clon' : {
|
||
<code class="methodname">CloneWindow</code>();
|
||
break;
|
||
}
|
||
default:
|
||
<code class="classname">BWindow</code>::<code class="methodname">MessageReceived</code>(<code class="parameter">m</code>);
|
||
break;
|
||
}
|
||
}
|
||
|
||
<span class="type">void</span> <code class="classname">AWindow</code>::<code class="methodname">NewColor</code>(<span class="type">rgb_color</span> <code class="parameter">col</code>) {
|
||
<code class="classname">BView</code>* <code class="varname">v</code> = <code class="methodname">FindView</code>("colorview");
|
||
if (<code class="varname">v</code> != <code class="constant">NULL</code>) {
|
||
<code class="varname">v</code>-><code class="methodname">SetViewColor</code>(<code class="parameter">col</code>);
|
||
<code class="varname">v</code>-><code class="methodname">Invalidate</code>();
|
||
}
|
||
}
|
||
</pre><p>
|
||
The trick is that the default target for a <code class="classname">BControl</code>
|
||
is its <code class="classname">BWindow</code>, and a
|
||
<code class="classname">BWindow</code> can find <code class="classname">BView</code>s by name.
|
||
It's also a good idea to do some dynamic
|
||
type-checking on the <code class="classname">BView</code> returned by
|
||
<code class="methodname">FindView()</code>.
|
||
</p><p>
|
||
Now let's look at some drawbacks of using archived interface objects:
|
||
</p><ul class="itemizedlist"><li><p>
|
||
You'll probably need one or two custom classes which are missing from the
|
||
current API. One is a <code class="classname">PictureView</code> (which simply
|
||
displays a <code class="classname">BPicture</code>, just as a
|
||
<code class="classname">BStringView</code> displays a String); another is a
|
||
<code class="classname">PictureRadioButton</code> (because the current
|
||
<code class="classname">BPictureButton</code> has no radio mode).
|
||
</p></li><li><p>
|
||
Unless you reverse-engineer their format, you won't be able to directly
|
||
create <code class="classname">BMessages</code> that contain archived
|
||
<code class="classname">BPicture</code>s. You'll have to create a
|
||
<code class="classname">BPicture</code> and <code class="methodname">Archive()</code> it.
|
||
</p></li><li><p>
|
||
For some reason, some <code class="classname">BView</code>s will ignore any
|
||
<code class="varname">ViewColor</code> and/or <code class="varname">LowColor</code> that's
|
||
stored in the archive, because those views adjust their
|
||
<code class="varname">ViewColor</code> and/or <code class="varname">LowColor</code> to match
|
||
those from their parent. Most of the time, this is what you want. If it's
|
||
not, you have two solutions: 1) you can manually override the colors in
|
||
your window constructor (see the example in
|
||
<code class="filename">Application.cpp</code>); or 2) you can enclose the
|
||
<code class="classname">BView</code> you want to "protect" into another
|
||
<code class="classname">BView</code> of the same size (see the example in
|
||
<code class="filename">AddAlternateResource.cpp</code>).
|
||
</p></li></ul></div><hr class="pagebreak" /><div class="sect1"><div xmlns="" xmlns:d="http://docbook.org/ns/docbook" class="titlepage"><div><div xmlns:d="http://docbook.org/ns/docbook"><h2 xmlns="http://www.w3.org/1999/xhtml" class="title"><a id="DevWorkshop4-6"></a>Developers' Workshop: Getting Ahead of Ourselves - Playing Movies With
|
||
the New Media Kit</h2></div><div xmlns:d="http://docbook.org/ns/docbook"><span xmlns="http://www.w3.org/1999/xhtml" class="author">By <span class="firstname">Eric</span> <span class="surname">Shepherd</span></span></div></div></div><p>
|
||
This is the first in a series of articles about programming with the new
|
||
Media Kit.
|
||
</p><p>
|
||
Soon there will be Media Kit add-ons available that provide support for
|
||
various video file formats. This week, let's take a look at how to play
|
||
back a movie using the new Media Kit and these add-ons.
|
||
</p><p>
|
||
You can download the code for this project at
|
||
ftp://ftp.be.com/pub/samples/media_kit/simplemovie.zip. It consists of
|
||
three C++ source files and two header files.
|
||
</p><p>
|
||
<code class="filename">main.cpp</code> instantiates the <code class="classname">MediaApp</code>
|
||
object, passing <code class="varname">argv[1]</code> to the object.
|
||
This argument should be the name of the movie to play (yes, this is a
|
||
command-line movie player, hence the name <code class="classname">SimpleMovie</code>).
|
||
</p><p>
|
||
The <code class="classname">MediaApp</code> constructor converts the movie's pathname into an <span class="type">entry_ref</span>
|
||
and passes that to a function called <code class="methodname">OpenRef()</code>, which will actually open
|
||
and begin playing the movie file. It also calls <code class="methodname">SetPulseRate()</code> to cause
|
||
the <code class="classname">MediaApp</code>::<code class="methodname">Pulse()</code> function to be called periodically; we'll use the
|
||
<code class="methodname">Pulse()</code> function to watch for the movie to end.
|
||
</p><p>
|
||
<code class="methodname">OpenRef()</code> looks like this:
|
||
</p><pre class="programlisting cpp">
|
||
<code class="classname">MediaPlayer</code> *<code class="classname">MediaApp</code>::<code class="methodname">OpenRef</code>(<span class="type">entry_ref *</span><code class="parameter">ref</code>) {
|
||
<code class="classname">MediaPlayer</code> *<code class="varname">player</code> = new <code class="classname">MediaPlayer</code>(<code class="parameter">ref</code>);
|
||
if (<code class="varname">player</code>-><code class="methodname">InitCheck</code>() != <code class="constant">B_OK</code>) {
|
||
<code class="function">printf</code>("Can't set up the player\n");
|
||
delete <code class="varname">player</code>;
|
||
return <code class="constant">NULL</code>;
|
||
}
|
||
return <code class="varname">player</code>;
|
||
}
|
||
</pre><p>
|
||
All it has to do is instantiate a <code class="classname">MediaPlayer</code> object
|
||
to represent the movie. The movie is started automatically by the
|
||
<code class="classname">MediaPlayer</code> constructor, so once this is done, the
|
||
movie is playing. If an error occurs setting up the movie,
|
||
<code class="classname">MediaPlayer</code>::<code class="methodname">InitCheck()</code> will return
|
||
<code class="constant">NULL</code>, and we throw away the object.
|
||
</p><p>
|
||
The <code class="methodname">Pulse()</code> function checks the movie periodically to see if it's done
|
||
playing:
|
||
</p><pre class="programlisting cpp">
|
||
<span class="type">void</span> <code class="classname">MediaApp</code>::<code class="methodname">Pulse</code>(<span class="type">void</span>) {
|
||
if (<code class="varname">player</code>) {
|
||
if (!<code class="varname">player</code>-><code class="methodname">Playing</code>()) {
|
||
<code class="methodname">SetPulseRate</code>(0); <span class="comment">// No more pulses, please</span>
|
||
delete <code class="varname">player</code>;
|
||
<code class="varname">player</code> = <code class="constant">NULL</code>;
|
||
<code class="methodname">PostMessage</code>(<code class="constant">B_QUIT_REQUESTED</code>);
|
||
}
|
||
}
|
||
else {
|
||
<code class="methodname">PostMessage</code>(<code class="constant">B_QUIT_REQUESTED</code>);
|
||
}
|
||
}
|
||
</pre><p>
|
||
This is done by calling the <code class="classname">MediaPlayer</code>::<code class="methodname">Playing()</code>
|
||
function. If it
|
||
returns <code class="constant">true</code>, the movie is still playing; otherwise, the movie is done.
|
||
If it's done, we turn off pulsing by calling <code class="code">SetPulseRate(0)</code>, delete the
|
||
player object (which, as we'll see later, closes the various media
|
||
connections), and post a <code class="constant">B_QUIT_REQUESTED</code> message to ourselves to quit
|
||
the application.
|
||
</p><p>
|
||
If the <code class="classname">MoviePlayer</code> object, player, wasn't
|
||
opened properly and is <code class="constant">NULL</code>, we
|
||
post a quit request to bail out immediately.
|
||
</p><p>
|
||
Now we get to the meaty, "we've been waiting for this for two months"
|
||
part: the <code class="classname">MediaPlayer</code> class. Before we begin, I should point out that
|
||
this class assumes that the video is encoded and that the audio is not,
|
||
and probably won't work right in any other case. It works fine for
|
||
playing typical Cinepak-encoded QuickTime movies, though. Because the
|
||
nodes aren't finished yet as I write this, this code isn't as flexible as
|
||
it could be.
|
||
</p><p>
|
||
The <code class="classname">MediaPlayer</code> constructor's job is to find an appropriate node to
|
||
process the movie file and instantiate a copy of the node to be used to
|
||
actually perform the playback. It sets <code class="varname">playingFlag</code> to <code class="constant">false</code> (we use this
|
||
flag to keep track of whether or not the movie is playing), and obtains a
|
||
<code class="classname">BMediaRoster</code>:
|
||
</p><pre class="programlisting cpp">
|
||
<code class="varname">roster</code> = <code class="classname">BMediaRoster</code>::<code class="methodname">Roster</code>(); <span class="comment">// Get the roster object</span>
|
||
</pre><p>
|
||
This static call in the <code class="classname">BMediaRoster</code> class returns a media roster we can
|
||
use. This object is used by all applications that interact with the Media
|
||
Kit; it acts as an intermediary between the application and the nodes
|
||
that provide media functionality.
|
||
</p><p>
|
||
Then it's necessary to identify a node that can handle the movie file
|
||
that the user specified. This is done by calling
|
||
<code class="classname">BMediaRoster</code>::<code class="methodname">SniffRef()</code>. This function looks at the file and returns a
|
||
<span class="type">dormant_node_info</span> structure that describes the add-on that's most
|
||
suitable for accessing the node.
|
||
</p><pre class="programlisting cpp">
|
||
<code class="varname">initStatus</code> = <code class="varname">roster</code>-><code class="methodname">SniffRef</code>(*<code class="varname">ref</code>, 0, &<code class="varname">nodeInfo</code>);
|
||
if (<code class="varname">initStatus</code>) {
|
||
return;
|
||
}
|
||
</pre><p>
|
||
Note that errors are stashed in a member variable called <code class="varname">initStatus</code>; the
|
||
<code class="methodname">InitStatus()</code> function returns this value so the application can determine
|
||
whether or not the <code class="classname">MediaPlayer</code> was properly initialized.
|
||
</p><p>
|
||
Once an appropriate add-on has been identified and information about it
|
||
stashed in <code class="varname">nodeInfo</code>, a node needs to be instantiated from that add-on.
|
||
Nodes located in media add-ons are referred to as "dormant nodes." We
|
||
call <code class="methodname">InstantiateDormantNode()</code> to accomplish this:
|
||
</p><pre class="programlisting cpp">
|
||
<code class="varname">initStatus</code> = <code class="varname">roster</code>-><code class="methodname">InstantiateDormantNode</code>(<code class="varname">nodeInfo</code>,
|
||
&<code class="varname">mediaFileNode</code>);
|
||
</pre><p>
|
||
Since the <span class="type">media_node</span> <code class="varname">mediaFileNode</code> is a file handling node, we next have
|
||
to tell the node what file to handle, by calling <code class="methodname">SetRefFor()</code>:
|
||
</p><pre class="programlisting cpp">
|
||
<code class="varname">roster</code>-><code class="methodname">SetRefFor</code>(<code class="varname">mediaFileNode</code>, *<code class="varname">ref</code>, <code class="constant">false</code>, &<code class="varname">duration</code>);
|
||
</pre><p>
|
||
This sets up the <code class="varname">mediaFileNode</code> to read from the specified <span class="type">entry_ref</span>. The
|
||
movie's length (in microseconds) is stored into the <span class="type">bigtime_t</span> variable
|
||
duration.
|
||
</p><p>
|
||
Now that the file node has been instantiated, it's time to instantiate
|
||
the other nodes needed to play back a movie. The <code class="methodname">Setup()</code> function handles
|
||
this; we'll look at it momentarily. After the <code class="methodname">Setup()</code> function returns
|
||
safely, <code class="classname">MediaPlayer</code>::<code class="methodname">Start()</code> is called to begin playback.
|
||
</p><p>
|
||
<code class="methodname">Setup()</code> begins by locating the standard audio mixer and video output
|
||
nodes. These will be used to actually play the movie to the speakers (or
|
||
other audio output device, as configured in the Audio preference panel)
|
||
and to a window on the screen (or other video output device, as
|
||
configured in the Video preference panel):
|
||
</p><pre class="programlisting cpp">
|
||
<code class="varname">err</code> = <code class="varname">roster</code>-><code class="methodname">GetAudioMixer</code>(&<code class="varname">audioNode</code>);
|
||
<code class="varname">err</code> = <code class="varname">roster</code>-><code class="methodname">GetVideoOutput</code>(&<code class="varname">videoNode</code>);
|
||
</pre><p>
|
||
Next, a time source is needed. 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. We get that using the following
|
||
code:
|
||
</p><pre class="programlisting cpp">
|
||
<code class="varname">err</code> = <code class="varname">roster</code>-><code class="methodname">GetTimeSource</code>(&<code class="varname">timeSourceNode</code>);
|
||
<code class="varname">b_timesource</code> = <code class="varname">roster</code>-><code class="methodname">MakeTimeSourceFor</code>(<code class="varname">timeSourceNode</code>);
|
||
</pre><p>
|
||
The first call obtains a <span class="type">media_node</span> representing the preferred time
|
||
source to which we'll slave our other nodes. The second call, to
|
||
<code class="methodname">MakeTimeSourceFor()</code>, actually obtains a
|
||
<code class="classname">BTimeSource</code> node object for that
|
||
time source. This will be used in making calculations related to timing
|
||
later on, that can't be done through the media roster.
|
||
</p><p>
|
||
You can think of a media node (represented by the <span class="type">media_node</span> structure)
|
||
as a component in a home theater system. 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.
|
||
</p><p>
|
||
The Media Kit works the same way. We need to locate audio outputs from
|
||
the <code class="varname">mediaFileNode</code> and find corresponding
|
||
audio inputs on the <code class="varname">audioNode</code>.
|
||
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 <code class="methodname">GetFreeOutputsFor()</code> to
|
||
find free output ports on the <code class="varname">mediaFileNode</code>,
|
||
and <code class="methodname">GetFreeInputsFor()</code> to
|
||
locate free input ports on the <code class="varname">audioNode</code>.
|
||
</p><pre class="programlisting cpp">
|
||
<code class="varname">err</code> = <code class="varname">roster</code>-><code class="methodname">GetFreeOutputsFor</code>(<code class="varname">mediaFileNode</code>,
|
||
&<code class="varname">fileAudioOutput</code>, 1, &<code class="varname">fileAudioCount</code>,
|
||
<code class="constant">B_MEDIA_RAW_AUDIO</code>);
|
||
|
||
<code class="varname">err</code> = <code class="varname">roster</code>-><code class="methodname">GetFreeInputsFor</code>(<code class="varname">audioNode</code>, &<code class="varname">audioInput</code>,
|
||
<code class="varname">fileAudioCount</code>, &<code class="varname">audioInputCount</code>,
|
||
<code class="constant">B_MEDIA_RAW_AUDIO</code>);
|
||
</pre><p>
|
||
We only want a single audio connection between the two nodes (a single
|
||
connection can carry stereo sound), and the connection is of type
|
||
<code class="constant">B_MEDIA_RAW_AUDIO</code>. On return, <code class="varname">fileAudioOutput</code>
|
||
and <code class="varname">audioInput</code> describe the
|
||
output from the <code class="varname">mediaFileNode</code> and the input into the <code class="varname">audioNode</code> that will
|
||
eventually be connected to play the movie's sound.
|
||
</p><p>
|
||
We likewise have to find a video output from the <code class="varname">mediaFileNode</code> and an
|
||
input into the <code class="varname">videoNode</code>. In this case, though, we expect the video
|
||
output from the <code class="varname">mediaFileNode</code> to be encoded, and the <code class="varname">videoNode</code> will want
|
||
to receive raw, uncompressed video. We'll work that out in a minute; for
|
||
now, let's just find the two ports:
|
||
</p><pre class="programlisting cpp">
|
||
<code class="varname">err</code> = <code class="varname">roster</code>-><code class="methodname">GetFreeOutputsFor</code>(<code class="varname">mediaFileNode</code>,
|
||
&<code class="varname">fileNodeOutput</code>, 1, &<code class="varname">fileOutputCount</code>,
|
||
<code class="constant">B_MEDIA_ENCODED_VIDEO</code>);
|
||
|
||
<code class="varname">err</code> = <code class="varname">roster</code>-><code class="methodname">GetFreeInputsFor</code>(<code class="varname">videoNode</code>, &<code class="varname">videoInput</code>,
|
||
<code class="varname">fileOutputCount</code>, &<code class="varname">videoInputCount</code>,
|
||
<code class="constant">B_MEDIA_RAW_VIDEO</code>);
|
||
</pre><p>
|
||
The problem we have now is that the <code class="varname">mediaFileNode</code> is outputting video
|
||
that's encoded somehow (in Cinepak format, for instance). The <code class="varname">videoNode</code>,
|
||
on the other hand, wants to display raw video. We need another node, in
|
||
between these, to decode the video (much like having an adapter to
|
||
convert <acronym class="acronym" title="Phase Alternating Line">PAL</acronym> video into
|
||
<acronym class="acronym" title="National Television System Committee">NTSC</acronym>,
|
||
for example). This node will be the codec
|
||
that handles decompressing the video into raw form.
|
||
</p><p>
|
||
We need to locate a codec node that can handle the video format being
|
||
output by the <code class="varname">mediaFileNode</code>. It's accomplished like this:
|
||
</p><pre class="programlisting cpp">
|
||
<code class="varname">nodeCount</code> = 1;
|
||
<code class="varname">err</code> = <code class="varname">roster</code>-><code class="methodname">GetDormantNodes</code>(&<code class="varname">nodeInfo</code>, &<code class="varname">nodeCount</code>,
|
||
&<code class="varname">fileNodeOutput</code>.<code class="varname">format</code>);
|
||
if (!<code class="varname">nodeCount</code>) {
|
||
<code class="function">printf</code>("Can't find the needed codec.\n");
|
||
return -1;
|
||
}
|
||
</pre><p>
|
||
This call to <code class="methodname">GetDormantNodes()</code> looks for a dormant node that can handle
|
||
the media format specified by the <code class="varname">fileNode</code>'s output <span class="type">media_format</span>
|
||
structure. Information about the node is returned in <code class="varname">nodeInfo</code>. <code class="varname">nodeCount</code>
|
||
indicates the number of matching nodes that were found. If it's zero, we
|
||
report that no codec was found.
|
||
</p><p>
|
||
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.
|
||
</p><p>
|
||
Then we use <code class="methodname">InstantiateDormantNode()</code> to instantiate the codec node, and
|
||
locate inputs into the node (that accept encoded video) and outputs from
|
||
the node (that output raw video):
|
||
</p><pre class="programlisting cpp">
|
||
<code class="varname">err</code> = <code class="varname">roster</code>-><code class="methodname">InstantiateDormantNode</code>(<code class="varname">nodeInfo</code>, &<code class="varname">codecNode</code>);
|
||
|
||
<code class="varname">err</code> = <code class="varname">roster</code>-><code class="methodname">GetFreeInputsFor</code>(<code class="varname">codecNode</code>, &<code class="varname">codecInput</code>, 1,
|
||
&<code class="varname">nodeCount</code>, <code class="constant">B_MEDIA_ENCODED_VIDEO</code>);
|
||
|
||
<code class="varname">err</code> = <code class="varname">roster</code>-><code class="methodname">GetFreeOutputsFor</code>(<code class="varname">codecNode</code>, &<code class="varname">codecOutput</code>, 1,
|
||
&<code class="varname">nodeCount</code>, <code class="constant">B_MEDIA_RAW_VIDEO</code>);
|
||
</pre><p>
|
||
Now we're ready to start connecting these nodes. 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.
|
||
</p><p>
|
||
We begin by connecting the file node's video output to the codec's input:
|
||
</p><pre class="programlisting cpp">
|
||
<code class="varname">tryFormat</code> = <code class="varname">fileNodeOutput</code>.<code class="varname">format</code>;
|
||
<code class="varname">err</code> = <code class="varname">roster</code>-><code class="methodname">Connect</code>(<code class="varname">fileNodeOutput</code>.<code class="varname">source</code>,
|
||
<code class="varname">codecInput</code>.<code class="varname">destination</code>, &<code class="varname">tryFormat</code>,
|
||
&<code class="varname">fileNodeOutput</code>, &<code class="varname">codecInput</code>);
|
||
</pre><p>
|
||
<code class="varname">tryFormat</code> indicates the format of the encoded video that
|
||
will be output by the <code class="varname">mediaFileNode</code>.
|
||
<code class="methodname">Connect()</code>, in essence, runs a wire between the
|
||
output from the media node's video output
|
||
(<code class="varname">fileNodeOutput</code>) to the codec node's input.
|
||
</p><p>
|
||
You may wonder what's up with the <code class="varname">fileNodeOutput.source</code> and
|
||
<code class="varname">codecInput.destination</code> structures. These <span class="type">media_source</span> and
|
||
<span class="type">media_destination</span> 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 <code class="methodname">Connect()</code> call (and time is money, especially in the media business).
|
||
</p><p>
|
||
Next it's necessary to connect the codec to the video output node. This
|
||
begins by setting up <code class="varname">tryFormat</code> to describe raw video of the same width
|
||
and height as the encoded video being fed into the codec, then calling
|
||
<code class="methodname">Connect()</code> to establish the connection:
|
||
</p><pre class="programlisting cpp">
|
||
<code class="varname">tryFormat</code>.<code class="varname">type</code> = <code class="constant">B_MEDIA_RAW_VIDEO</code>;
|
||
<code class="varname">tryFormat</code>.<code class="varname">u</code>.<code class="varname">raw_video</code> = <span class="type">media_raw_video_format</span>::<code class="varname">wildcard</code>;
|
||
<code class="varname">tryFormat</code>.<code class="varname">u</code>.<code class="varname">raw_video</code>.<code class="varname">display</code>.<code class="varname">line_width</code> =
|
||
<code class="varname">codecInput</code>.<code class="varname">format</code>.<code class="varname">u</code>.<code class="varname">encoded_video</code>.<code class="varname">output</code>.<code class="varname">display</code>.<code class="varname">line_width</code>;
|
||
<code class="varname">tryFormat</code>.<code class="varname">u</code>.<code class="varname">raw_video</code>.<code class="varname">display</code>.<code class="varname">line_count</code> =
|
||
<code class="varname">codecInput</code>.<code class="varname">format</code>.<code class="varname">u</code>.<code class="varname">encoded_video</code>.<code class="varname">output</code>.<code class="varname">display</code>.<code class="varname">line_count</code>;
|
||
|
||
<code class="varname">err</code> = <code class="varname">roster</code>-><code class="methodname">Connect</code>(<code class="varname">codecOutput</code>.<code class="varname">source</code>,
|
||
<code class="varname">videoInput</code>.<code class="varname">destination</code>, &<code class="varname">tryFormat</code>, &<code class="varname">codecOutput</code>,
|
||
&<code class="varname">videoInput</code>);
|
||
</pre><p>
|
||
Now we connect the audio from the media file to the audio mixer node. We
|
||
just copy the <span class="type">media_format</span> from the file's audio output, since both ends
|
||
of the connection should exactly match.
|
||
</p><pre class="programlisting cpp">
|
||
<code class="varname">tryFormat</code> = <code class="varname">fileAudioOutput</code>.<code class="varname">format</code>;
|
||
<code class="varname">err</code> = <code class="varname">roster</code>-><code class="methodname">Connect</code>(<code class="varname">fileAudioOutput</code>.<code class="varname">source</code>,
|
||
<code class="varname">audioInput</code>.<code class="varname">destination</code>, &<code class="varname">tryFormat</code>,
|
||
&<code class="varname">fileAudioOutput</code>, &<code class="varname">audioInput</code>);
|
||
</pre><p>
|
||
The last step of configuring the connections is to ensure that all the
|
||
nodes are slaved to the preferred time source:
|
||
</p><pre class="programlisting cpp">
|
||
<code class="varname">err</code> = <code class="varname">roster</code>-><code class="methodname">SetTimeSourceFor</code>(<code class="varname">mediaFileNode</code>.<code class="varname">node</code>,
|
||
<code class="varname">timeSourceNode</code>.<code class="varname">node</code>);
|
||
<code class="varname">err</code> = <code class="varname">roster</code>-><code class="methodname">SetTimeSourceFor</code>(<code class="varname">videoNode</code>.<code class="varname">node</code>,
|
||
<code class="varname">timeSourceNode</code>.<code class="varname">node</code>);
|
||
<code class="varname">err</code> = <code class="varname">roster</code>-><code class="methodname">SetTimeSourceFor</code>(<code class="varname">codecOutput</code>.<code class="varname">node</code>.<code class="varname">node</code>,
|
||
<code class="varname">timeSourceNode</code>.<code class="varname">node</code>);
|
||
</pre><p>
|
||
The <code class="methodname">Start()</code> 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 (<code class="varname">audioNode</code>), the
|
||
media file's node (<code class="varname">mediaFileNode</code>), the codec, and the video node. 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:
|
||
</p><pre class="programlisting cpp">
|
||
<code class="varname">err</code> = <code class="varname">roster</code>-><code class="methodname">GetStartLatencyFor</code>(<code class="varname">timeSourceNode</code>,
|
||
&<code class="varname">startTime</code>);
|
||
|
||
<code class="varname">startTime</code> +=
|
||
<code class="varname">b_timesource</code>-><code class="methodname">PerformanceTimeFor</code>(<code class="classname">BTimeSource</code>::<code class="methodname">RealTime</code>()
|
||
+ 1000000 / 50);
|
||
</pre><p>
|
||
The <code class="classname">BTimeSource</code>::<code class="methodname">RealTime()</code> 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 when the
|
||
movie will begin (basically a 50th of a second from "now"). This value is
|
||
saved in <code class="varname">startTime</code>. These are added to the value returned by
|
||
<code class="methodname">GetStartLatencyFor()</code>, which returns the time required to actually start
|
||
the time source and all the nodes slaved to it.
|
||
</p><p>
|
||
Then we simply call <code class="classname">BMediaRoster</code>::<code class="methodname">StartNode()</code> for each node, specifying
|
||
<code class="varname">startTime</code> as the performance time when playback should begin:
|
||
</p><pre class="programlisting cpp">
|
||
<code class="varname">err</code> = <code class="varname">roster</code>-><code class="methodname">StartNode</code>(<code class="varname">mediaFileNode</code>, <code class="varname">startTime</code>);
|
||
<code class="varname">err</code> = <code class="varname">roster</code>-><code class="methodname">StartNode</code>(<code class="varname">codecNode</code>, <code class="varname">startTime</code>);
|
||
<code class="varname">err</code> = <code class="varname">roster</code>-><code class="methodname">StartNode</code>(<code class="varname">videoNode</code>, <code class="varname">startTime</code>);
|
||
</pre><p>
|
||
And we set <code class="varname">playingFlag</code> to <code class="constant">true</code>,
|
||
since we've now begun playback. At this
|
||
point (actually, a 50th of a second after this point, but who's
|
||
counting?), the movie will begin playing.
|
||
</p><p>
|
||
Notice that we don't start the audio mixer node (<code class="varname">audioNode</code>), and, as
|
||
we'll see shortly, we don't stop it, either. The audio mixer node is
|
||
always running, and an application should never stop it.
|
||
</p><p>
|
||
The <code class="methodname">Stop()</code> function stops movie playback by
|
||
calling <code class="methodname">StopNode()</code> for each
|
||
node. It then sets <code class="varname">playingFlag</code> to <code class="constant">false</code>:
|
||
</p><pre class="programlisting cpp">
|
||
<code class="varname">err</code> = <code class="varname">roster</code>-><code class="methodname">StopNode</code>(mediaFileNode, 0, true);
|
||
<code class="varname">err</code> = <code class="varname">roster</code>-><code class="methodname">StopNode</code>(codecNode, 0, true);
|
||
<code class="varname">err</code> = <code class="varname">roster</code>-><code class="methodname">StopNode</code>(videoNode, 0, true);
|
||
<code class="varname">playingFlag</code> = <code class="constant">false</code>;
|
||
</pre><p>
|
||
The <code class="varname">true</code> values indicate that the nodes should stop playing
|
||
immediately, instead of at some time in the future. If we wanted the
|
||
nodes to stop in sync with each other, we could compute a performance
|
||
time slightly in the future, and specify that time instead.
|
||
</p><p>
|
||
The <code class="methodname">Playing()</code> function determines whether or not the movie is still
|
||
playing. Since we don't provide a pause feature, we can do this simply by
|
||
looking to see whether or not the current performance time is at or past
|
||
the end of the movie:
|
||
</p><pre class="programlisting cpp">
|
||
<code class="varname">currentTime</code> =
|
||
<code class="varname">b_timesource</code>-><code class="methodname">PerformanceTimeFor</code>(<code class="classname">BTimeSource</code>::<code class="methodname">RealTime</code>());
|
||
|
||
if (<code class="varname">currentTime</code> >= <code class="varname">startTime</code>+<code class="varname">duration</code>) {
|
||
<code class="varname">playingFlag</code> = <code class="constant">false</code>;
|
||
}
|
||
</pre><p>
|
||
Here we obtain the current performance time by taking the real time and
|
||
passing it through <code class="methodname">PerformanceTimeFor()</code>, then look to see if it's equal
|
||
to or greater than the starting time plus the movie's duration. If it is,
|
||
the movie's done playing.
|
||
</p><p>
|
||
Last, but not least, let's look at the <code class="classname">MediaPlayer</code> class destructor. It
|
||
handles disconnecting the nodes from one another:
|
||
</p><pre class="programlisting cpp">
|
||
<code class="methodname">Stop</code>();
|
||
<code class="varname">err</code> = <code class="varname">roster</code>-><code class="methodname">Disconnect</code>(<code class="varname">mediaFileNode</code>.<code class="varname">node</code>,
|
||
<code class="varname">fileNodeOutput</code>.<code class="varname">source</code>, <code class="varname">codecNode</code>.<code class="varname">node</code>,
|
||
<code class="varname">codecInput</code>.<code class="varname">destination</code>);
|
||
|
||
<code class="varname">err</code> = <code class="varname">roster</code>-><code class="methodname">Disconnect</code>(<code class="varname">codecNode</code>.<code class="varname">node</code>,
|
||
<code class="varname">codecOutput</code>.<code class="varname">source</code>, <code class="varname">videoNode</code>.<code class="varname">node</code>,
|
||
<code class="varname">videoInput</code>.<code class="varname">destination</code>);
|
||
|
||
<code class="varname">err</code> = <code class="varname">roster</code>-><code class="methodname">Disconnect</code>(<code class="varname">mediaFileNode</code>.<code class="varname">node</code>,
|
||
<code class="varname">fileAudioOutput</code>.<code class="varname">source</code>, <code class="varname">audioNode</code>.<code class="varname">node</code>,
|
||
<code class="varname">audioInput</code>.<code class="varname">destination</code>);
|
||
</pre><p>
|
||
This code makes sure playback is stopped (disconnecting currently playing
|
||
nodes is discouraged), then disconnects each of the three connections we
|
||
established in <code class="methodname">Setup()</code>. Then we need to let the Media Kit know we're done
|
||
with the nodes by releasing them. We don't release the video and mixer
|
||
nodes because we didn't actually instantiate them ourselves.
|
||
</p><pre class="programlisting cpp">
|
||
<code class="varname">roster</code>-><code class="methodname">ReleaseNode</code>(<code class="varname">codecNode</code>);
|
||
<code class="varname">roster</code>-><code class="methodname">ReleaseNode</code>(<code class="varname">mediaFileNode</code>);
|
||
</pre><p>
|
||
Hopefully this sample code will be helpful in learning to write software
|
||
that uses the new Media Kit for playback of media data. Updates to the
|
||
Media Kit chapter of the Be Book are coming over the next few weeks (a
|
||
large <code class="classname">BMediaRoster</code> update is being reviewed for accuracy now and should
|
||
be available soon). Have fun!
|
||
</p></div><hr class="pagebreak" /><div class="sect1"><div xmlns="" xmlns:d="http://docbook.org/ns/docbook" class="titlepage"><div><div xmlns:d="http://docbook.org/ns/docbook"><h2 xmlns="http://www.w3.org/1999/xhtml" class="title"><a id="Gassee4-6"></a>More Browser Integration Bedtime Stories</h2></div><div xmlns:d="http://docbook.org/ns/docbook"><span xmlns="http://www.w3.org/1999/xhtml" class="author">By <span class="firstname">Jean-Louis</span> <span class="surname">Gassée</span></span></div></div></div><p>
|
||
Last week's column, "Another Bedtime Story" , triggered more mail than
|
||
usual. Most of it was positive, with some requests for clarification of
|
||
our position. Are we against browser integration—yes, no, and why? To
|
||
paraphrase a highly placed government official, it depends what the
|
||
meaning of "integration" is—and, one might add for precision, the
|
||
intent, manner and consequences of integration.
|
||
</p><p>
|
||
What we have today with <span class="application">Explorer</span> and Windows is what I'd call tentacular
|
||
integration. (I won't say here what the spellchecker on my diminutive
|
||
Toshiba Libretto offers as an alternative for tentacular, a word it
|
||
pretends not to know. It is in the American Heritage Dictionary and my
|
||
Libretto will run the BeOS once we get Toshiba to release the specs of
|
||
their sound chip.)
|
||
</p><p>
|
||
There is a single word for <span class="application">Explorer</span>, but the software itself has many
|
||
tentacles that reach deep inside the system. As a result, Microsoft seems
|
||
to be saying, a lot of damage will occur if you forcibly remove <span class="application">Explorer</span>.
|
||
Specifically, when the now famous "Felten program" attempted to remove
|
||
<span class="application">Explorer</span>, Microsoft wanted to show how Web access and navigation were
|
||
still possible, if you knew your way around Windows Registry, where a
|
||
myriad of settings are stored. <span class="application">Explorer</span> was so tightly integrated with
|
||
Windows that one couldn't really remove it, just hide and disable it.
|
||
</p><p>
|
||
Furthermore, as a result of this failed "<span class="application">Explorer</span>-ectomy," performance
|
||
suffered greatly. Sorry about that, said Microsoft, it's an imperfect
|
||
world, but through our efforts consumers benefit from tight integration.
|
||
</p><p>
|
||
I won't add to the sum of ironies provoked by the videotapes that tried
|
||
to persuade the courts of justice and public opinion of the soundness of
|
||
this argument. Thinking of Redmond's serene and understated culture, one
|
||
can imagine the sharing of thoughts following the discovery of
|
||
unfortunate editing miscues.
|
||
</p><p>
|
||
But if, as Microsoft contends, <span class="application">Explorer</span> is not a separate application so
|
||
much as an operating system upgrade—hence its size, depth, and
|
||
complexity—one might be tempted to explore that line of reasoning.
|
||
That is, the Web is important and is now tightly woven into our everyday
|
||
lives. Certainly, a mere application cannot do justice to such an
|
||
important and pervasive function. That responsibility belongs to the
|
||
platform, to the operating system.
|
||
</p><p>
|
||
Perhaps. But couldn't one make a similar argument about word processing,
|
||
spreadsheets, or presentations? Shouldn't these important functions also
|
||
be integrated into the operating system? Come to think of it, if
|
||
Microsoft Office isn't integrated with Windows, it comes pretty close for
|
||
millions of users who receive Office as a bundled, if not integrated,
|
||
product. The technical argument that one must "integrate" <span class="application">Explorer</span>, while
|
||
one can merely "bundle" Office doesn't withstand technical scrutiny. Take
|
||
two identical PCs loaded with a late version of Windows 95, OSR 2.1, or
|
||
later. Install (integrate if you prefer) <span class="application">Explorer</span> on one and Navigator on
|
||
the other, and compare performance. There's no significant difference.
|
||
</p><p>
|
||
If one reads carefully through Mr. Allchin's deposition and the language
|
||
used in various video demonstrations, one sees the words "rich" and
|
||
"richness" used to describe the improved user experience that results
|
||
from making elements of the user interface more Web-like. This poses
|
||
another question regarding the "how" of integrating Web capabilities
|
||
inside the operating system. To oversimplify a bit, couldn't Microsoft
|
||
have broken the job in two pieces—a system module and an application
|
||
module? This approach would make removing the application module a simple
|
||
and safe task. It would also make installing another browser—while
|
||
preserving the "richness" afforded to other applications by the system
|
||
module—easier. One might ask whether Microsoft had non-technical
|
||
motives in choosing a tentacular implementation instead of the modular
|
||
one.
|
||
</p><p>
|
||
Lastly, in reading the court transcript, Microsoft seems to allege that
|
||
the BeOS <span class="application">NetPositive</span> browser is "integrated," and cites the help system
|
||
as proof. If you trash <span class="application">NetPositive</span>, a simple operation, the help system
|
||
no longer works: it needs a now absent HTML interpreter. Ergo,
|
||
<span class="application">NetPositive</span> is integrated.
|
||
</p><p>
|
||
Assume for a moment that the help system is written in the format of an
|
||
editor supplied with the OS. If you delete the editor, you can no longer
|
||
read the help files. Unless, of course, you use a third-party editor.
|
||
Does this mean that the supplied editor is integrated with the OS? Of
|
||
course not. And what will Microsoft say when one or more of the
|
||
third-party browsers currently under development for the BeOS become
|
||
available and read the help file or the BeOS documentation—even if
|
||
<span class="application">NetPositive</span> is missing? Will that make them—ipso facto—integrated
|
||
with the BeOS?
|
||
</p></div></div><div id="footer"><hr /><div id="footerT">Prev: <a href="Issue4-5.html">Issue 4-5, February 3, 1999</a> Up: <a href="volume4.html">Volume 4: 1999</a> Next: <a href="Issue4-7.html">Issue 4-7, February 17, 1999</a> </div><div id="footerB"><div id="footerBL"><a href="Issue4-5.html" title="Issue 4-5, February 3, 1999"><img src="./images/navigation/prev.png" alt="Prev" /></a> <a href="volume4.html" title="Volume 4: 1999"><img src="./images/navigation/up.png" alt="Up" /></a> <a href="Issue4-7.html" title="Issue 4-7, February 17, 1999"><img src="./images/navigation/next.png" alt="Next" /></a></div><div id="footerBR"><div><a href="http://www.haiku-os.org"><img src="./images/People_24.png" alt="haiku-os.org" title="Visit The Haiku Website" /></a></div><div class="navighome" title="Home"><a accesskey="h" href="index.html"><img src="./images/navigation/home.png" alt="Home" /></a></div></div><div id="footerBC"><a href="http://www.access-company.com/home.html" title="ACCESS Co."><img alt="Access Company" src="./images/access_logo.png" /></a></div></div></div><div id="licenseFooter"><div id="licenseFooterBL"><a rel="license" href="http://creativecommons.org/licenses/by-nc-nd/3.0/" title="Creative Commons License"><img alt="Creative Commons License" style="border-width:0" src="https://licensebuttons.net/l/by-nc-nd/3.0/88x31.png" /></a></div><div id="licenseFooterBR"><a href="./LegalNotice.html">Legal Notice</a></div><div id="licenseFooterBC"><span id="licenseText">This work is licensed under a
|
||
<a rel="license" href="http://creativecommons.org/licenses/by-nc-nd/3.0/">Creative
|
||
Commons Attribution-Non commercial-No Derivative Works 3.0 License</a>.</span></div></div></body></html>
|