TweetFollow Us on Twitter

June 96 - Connecting Users with QuickTime Conferencing

Connecting Users With QuickTime Conferencing

DEAN BLACKKETTER

QuickTime Conferencing (QTC) is a new Apple technology that helps developers add real-time sharing of sound, video, and data to their applications. This overview suggests the different ways you can use QTC to help users collaborate. The article describes the components that most developers will need to use to take advantage of QTC and discusses Watcher and Caster, two QTC applications that enable users to tune into network broadcasts and create broadcasts for others to view.

Video telephones abound in science fiction movies. From Buck Rogers to Star Trek, visions of the future show people communicating visually over long distances. This futuristic technology is available to Macintosh developers and users now. QuickTime Conferencing provides a platform for developers to easily enable users to share sound, video, and data across a variety of networks.

QTC ships with selected Power Macintosh computers and with some hardware bundles, and can be licensed by developers to ship with their applications. Apple provides a basic videoconferencing application, Apple Media Conference (AMC), and developers are encouraged to create QTC applications that interoperate with AMC and add cool new collaborative features.

This article will give you background information on the QTC architecture, tell you about the components that make up that architecture, and then describe in detail the workings of two simple QTC applications, Watcher and Caster, that enable the user to watch audio and video and to broadcast them onto a network. This issue's CD contains the source code for these applications as well as the QTC documentation and the extension and header files.

QUICKTIME CONFERENCING -- THE BIG PICTURE

QuickTime Conferencing provides a platform for building Macintosh applications that can send and receive audio, video, and data between computers connected on a network. QTC supports basic two-way audio communication and a video "telephone" type of connection, and it supports a wide variety of other models as well. One of the goals of QTC is to provide developers with a set of tools that make it easy to add real-time media sharing across a number of different kinds of networks.

This opens up the possibility of adding sound and video to multiuser applications where it would have been prohibitively difficult before -- and these don't have to be conventional telephony-style applications. Imagine a flight simulator that allows you to talk with your fellow squadron members, or a groupware document-markup application that lets your fellow editors see your expression upon examining the latest changes. Picture a regional educational system that enables dozens of students to tune into an 8 A.M. lecture from their dorm rooms across campus or across the state. This isn't the stuff of science fiction anymore.

QTC uses many of the services provided by QuickTime itself and shares an architectural basis in the Component Manager. QTC takes advantage of the Image Compression Manager for video compression and decompression, the sequence grabber components for capturing media, and the Movie Toolbox for recording movies to disk. When new features and improvements are added to QuickTime, they often can be used by QTC immediately. For example, components created for video or sound compression in QuickTime are automatically available to QTC.

CONFERENCE CONFIGURATIONS

QTC's basic metaphor for real-time media connections is that of a conference. Conferences are quite flexible and can be configured in a variety of ways. They can have one, a few, or many members, connected symmetrically or asymmetrically. As illustrated in Figure 1, connections can take one of three forms: point to point, for two-way conferences; multipoint, for virtual meetings and groupware applications; or broadcast, for transmitting from one member to many others.

Figure 1. The three types of conference connections

Members can send or receive sound, video, or data. Media types can be added, removed, or changed during a conference. Members can join or leave a conference at any time. Conferences can be merged, and data can be sent to one or all of the conference members.

Depending on the application, you may want to give users a single configuration -- say, a two-way audio and video connection -- or allow them to modify the conference configuration themselves. QTC was designed to support a wide variety of conference configurations and to leave it up to developers to decide which features they need. Indeed, some applications may need to switch between different configurations within a single conference. The applications described later in this article each operate in a single configuration; one can broadcast video and sound to an unlimited number of recipients and the other can tune into one or more broadcast conferences.

NETWORK, PROTOCOL, AND MEDIA INDEPENDENCE

QTC is network, protocol, and media independent. This means that applications don't have to know the specifics of a particular network to set up a QTC conference. QTC 1.0.2 ships with support for TCP/IP and AppleTalk networks; third parties and Apple are working on adding new networks like ISDN, isoEthernet, and ATM to the list. QTC 1.0.2 supports a new media-oriented network protocol, called MovieTalk, but can also support other media protocols such as the ITU H.320 standard and the emerging standards used on the Internet Multicast Backbone (MBONE).

The media that flows between conference members is organized into one or more streams of a particular media type. QTC 1.0.2 supports sound and video streams, which can be compressed with any sound or video compressor. Future versions of QTC will be able to support other media types, such as music and text, to parallel the different track types that can be stored in a QuickTime movie.

THE CONFERENCING EXPERIENCE

QTC provides some of the basic user interface elements called for in a conferencing application. For example, each member of a conference can be represented on the screen with a stream controller, in much the same way that a QuickTime movie controller provides a control representation for a QuickTime movie. In fact, the stream controller and the movie controller share a similar user interface, so that a user who has some experience with one can apply that knowledge to the other.

QTC also provides a standard user interface enabling users to choose who to call and include in a QTC conference, in the form of browser components. Browsers work a bit like the Standard File Package that allows users to open and save files: they provide a standard interface for choosing fellow users or searching through PowerTalk catalogs to find other conference members and place calls to them.

QUICKTIME CONFERENCING COMPONENTS

QTC, like much of QuickTime, is built of Component Manager components. Apple provides a basic suite of components that enable the user to share data and send and receive compressed video and audio on a few different networks. Before we dive into our example applications, let's go over some of the component types that make up the QTC component suite.

There are three main types of QTC components that most developers will need to know about to add QTC support to their applications: the conference component, the stream controller component, and the browser component. I'll describe these in some detail. Developers who want to do fancier things will probably need to know about some of the other components; the key ones are briefly described later.

Because of the modular architecture of QTC, developers can add, extend, or replace features and components. For example, a developer who wants to add support for a new network multimedia protocol can create a new transport component and register it with the Component Manager. Applications can then find that component and specify its use in a conference. Developers who want to improve on the QTC stream controller can capture the standard controller, delegate many of the functions, and replace the ones of interest.

THE CONFERENCE COMPONENT

The conference component is the key player in a QTC conference. It acts as a central hub and does the bulk of the work required to orchestrate the comings and goings of the conference. It's responsible for listening in on the various networks, placing and answering calls, managing and merging multiple conferences, and more. The conference component can also provide some higher-level functionality, such as setting up media capture, handling user events, and even creating and managing conference windows.

Applications create a conference component instance and let the conference component do much of the work needed to create, manage, and end conferences. Applications can then tell the conference component to listen on the networks for incoming calls or to place a call to another member.

Conference components create conference events when they need to express some change in a conference to the application. For example, when an incoming call is made to a conference, the conference component will generate an event of type mtIncomingCallEvent to notify the application of the call. Applications call the component routine MTConferenceGetNextEvent periodically to get the events from the conference component, much as applications call the system routine WaitNextEvent to get user and system events from the Event Manager.

In response to these conference events, applications work with the conference component to respond appropriately -- for example, creating a window to display a new conference member or send messages to other conference members. Details of working with the conference component will be discussed later when we look at our sample applications, Watcher and Caster.

THE STREAM CONTROLLER COMPONENT

Stream controllers are responsible for handling the default user interface for controlling QTC media streams as well as managing their display on the screen and through the speaker. The conference component is responsible for creating and managing stream controller components. Applications are passed references to the stream controllers by the conference component so that they can keep track of where and how the media is being displayed.

The standard stream controller looks quite a bit like the standard QuickTime movie controller, with buttons to control the flow of media, resize the visual portion of the stream, and adjust the sound levels. The stream controller adds some utility buttons that the movie controller doesn't have: a snapshot button for capturing the current image displayed in the controller and a record button that provides a standard way for a user to record the media in a stream controller. (The conference component or the application is responsible for actually handling the snapshots or recorded movies after the controller has initiated them.)

Controllers associated with the sending side of a media stream (known as source controllers) have a slightly different appearance and behavior from those associated with the receiving side (known as sink controllers), as shown in Figure 2. The source controller may have a microphone "gain" button that's animated to indicate the level of the audio being sent across the connection. Users who click this button can adjust the volume of the sound being sent across the connection. On the receiving end, the sink controller may display a volume control button that behaves like the speaker button on the standard movie controller, allowing the user to adjust the volume of the incoming stream.

Figure 2. Source and sink controller user interfaces

THE BROWSER COMPONENT

To place a call or add another member to a conference, the user needs to specify the other member to call. Browser components provide a simple way for users to browse the network and identify other members. Browser components come in two flavors: network-specific browsers and the PowerTalk browser. The PowerTalk browser and browsers specific to TCP/IP and AppleTalk are shown in Figure 3.

Figure 3. Browsers

For each different network type -- such as TCP/IP or AppleTalk -- unique browser components are provided that allow the user to specify a network-specific address. For example, as shown in Figure 3, the AppleTalk browser presents the user with a Chooser-style interface whereby the user can choose the zone and then the registered name within that zone on an AppleTalk network, similar to using the Chooser to pick a LaserWriter on an AppleTalk network. The TCP/IP browser provides a simple type-in interface that can accept TCP/IP addresses in numerical or text form.

The PowerTalk browser, on the other hand, is considered a generic or universal browser, not tied to a particular network or addressing scheme. Users who have PowerTalk installed can take advantage of the various PowerTalk catalogs and business cards; these provide an integrated way for users to organize and find other QTC users in the same way that they access electronic mail addresses via PowerTalk. The PowerTalk browser allows the user to choose a business card from a PowerTalk catalog that contains a QTC entry (provided by the QTC PowerTalk Template). This works for local user catalogs and catalogs provided by PowerShare servers, as well as the generic AppleTalk network catalog, which allows the user to look out onto the network and into AppleTalk zones for other users. Users can edit their personal catalogs from within the Finder, consistent with the standard PowerTalk human interface.



    ABOUT APPLETALK MULTICAST

    Digital video and sound can generate a great deal of data, even when compressed. Hard disk space is getting to be quite cheap, but network bandwidth is still an expensive and shared commodity. To keep your fellow users and network administrators happy, we developed multicast extensions to AppleTalk that allow a single copy of QuickTime Conferencing media sent out onto a network to be received and displayed by any number of users.

    AppleTalk Multicast consists of a special packet format and a routing protocol that makes efficient use of the network bandwidth. On a single network segment, AppleTalk Multicast uses multicast packets that can be received by anyone on that local network. On an AppleTalk internet, multicast-aware routers communicate with each other with a new protocol called SMRP, the Simple Multicast Routing Protocol, as shown in Figure 4. The routers deliver copies of the media data only to other networks in which there's a user who wants to receive that data. Networks with no users interested in the broadcast aren't burdened with the network usage.

    Apple has licensed AppleTalk Multicast and the SMRP protocol to Cisco Systems, Inc. Cisco's router software as of version 11.0 supports this multimedia protocol.

    Figure 4. AppleTalk Multicast routing


OTHER QUICKTIME CONFERENCING COMPONENTS

QTC defines and uses many other kinds of components besides the three just mentioned. Several of these component types may be of interest to developers who want to add support for new networks or new media protocols; others may be of use to developers who want to have more control over their conferences. Some of these are listed here.
  • Stream director components are responsible for managing the media streams that flow between conference members. Stream directors are of two types: source stream directors and sink stream directors. Source stream directors work with media sources, such as QuickTime sequence grabbers, to capture audio and video data to be sent across the network. Sink stream directors are responsible for setting up and displaying incoming media data: video to the screen and sound to the speaker. Conference components and controller components handle most of the management and control of stream directors.

  • Transport components are responsible for implementing the network protocol that communicates media data, formats, and control information. MovieTalk, the default QTC protocol, is implemented as a transport component. Apple's H.320/ISDN conferencing card adds another transport type that supports the ITU H.320 video conferencing standard. Developers who want to support new media protocols can create new transport components to translate the control messages from a conference into messages appropriate for the new protocol and vice versa.

  • Network components contain code specific to a given network type. QTC 1.0.2 provides network components for AppleTalk and TCP/IP. Future versions of QTC will provide direct OpenTransport network interfaces as well as others. Network components can provide access to multicast services on some shared networks so that media data can be sent to multiple recipients without having to send out multiple copies of that data. (See "About AppleTalk Multicast" for a discussion of one such multicast service.) The conference component automatically takes advantage of multicast network services when they're available.

  • Recorder components attach to stream directors and provide a mechanism to record to disk the media sent or received within a conference. Apple provides a recorder component that records media into QuickTime movies and can attach to multiple members via stream directors to create movies of entire conferences at once.
Several other components are used within QTC, including player components, flow control components, and others of interest to developers who want to extend QTC to support new networks, protocols, and media. Figure 5 shows how a number of QTC components typically work together within the all-encompassing conference component. For information on all of the components that make up QTC, check out the QTC documentation on this issue's CD.

Figure 5. How QTC components work together within the conference component

TUNING IN WITH WATCHER

Probably the best way to show how to use QTC in an application is with some examples, so we've created Watcher and Caster. Watcher lets the user tune into broadcasts on AppleTalk networks, while Caster enables the user to create broadcasts that can be watched by others on the AppleTalk network. Watcher and Caster are compatible with Apple Media Conference (AMC), the QTC application that Apple ships with selected CPUs and product packages, so you can use Watcher to watch a broadcast that's being sent by AMC or Caster, and you can use Caster to create broadcasts that can be received by Watcher and AMC.

Note that in several places in Watcher and Caster, we do some work manually that otherwise could be done automatically by the conference component. We do this extra work to demonstrate how you can customize an application if the behavior that you want is different from the default behavior offered by the conference component.

HOW WATCHER WORKS

Watcher is a relatively simple Macintosh application. After setting up the application environment, Watcher sets up the conference component that will place calls and manage the incoming media. Then, within the event loop, the application checks for user and system events and also checks the conference component for conference events, which indicate changes in the conference state and may require responses from the application.

The overall flow of Watcher or any QTC application that uses the conference component is as follows:

QTCApp() 
{
   SetupApplication();
   SetupConferenceComponent();
   StartListening();
   do {
      ProcessUserEvents();
      ProcessConferenceEvents();
   } while (!gQuit);
   CleanUpConferenceComponent();
   CleanUpApplication();
   ExitToShell();
}
Below, I'll go into more detail about the three major application responsibilities -- setting up the conference component, handling conference events, and cleaning up at the end of the conference -- showing the core routines that deal directly with the conference component. Check out the full source code to see them in the context of the entire application.

SETTING UP A CONFERENCE

Listing 1 shows how the conference component is created and initially configured. The Component Manager call OpenDefaultComponent is used to create and open an instance of the conference component; then the conference component mode is set to indicate that the conference will be used to receive media. Finally, the component is told what networks to prepare for connections on -- AppleTalk in this case -- and how to identify itself on that network.

MTConferenceListen (as well as MTBrowserBrowse, a call we'll encounter a little later) uses a C string of type MTCString to describe the network and transport configurations. In Listing 1, the string "mtlkatlk\tNoIncomingCalls\x0D" indicates that the conference component should listen for calls that have a transport subtype of 'mtlk' (the component subtype for the MovieTalk transport component) and a network subtype of 'atlk' (the subtype for AppleTalk networks). The "\t" delimits the subtypes from the network-specific configuration data that follows. For AppleTalk networks, this is the Name Binding Protocol (NBP) type "No Incoming Calls." Finally, the configuration is terminated with a carriage return ("\x0D"). You can string together multiple configuration strings (each terminated with a carriage return) to listen in on multiple networks for calls. Check out the full documentation for a more complete explanation of the configuration strings.

Listing 1. CreateWatchConference

ComponentResult CreateWatchConference(MTCString63 userName)
{
   ComponentResult   err;
   
   /* Create a conference record. */
   err = NewConference(&gConference);
   if (err == noErr) {
      gConference->confComponent
         = OpenDefaultComponent(kMTConferenceType,
              kMTMovieTalkSubType);
      if (gConference->confComponent) {
         /* Tell the conference component that we only want to */
         /* receive media, not send. */
         err = MTConferenceSetMode(gConference->confComponent,
                  mtReceiveMediaModeMask);
         /* Tell the conference component to prepare to use
            AppleTalk.
            The funky C string tells the conference component:
               mtlk = use the MovieTalk transport component
               atlk = use the AppleTalk network component
               NoIncomingCalls = the AppleTalk-specific NBP type
               that's used for listening;
                i.e., there will be no incoming calls 
         */
         if (err == noErr)
            err = MTConferenceListen(gConference->confComponent,
                      userName /* User name */, 
                      userName /* Service name */,
                      (MTCString)"mtlkatlk\tNoIncomingCalls\x0D");
      }
      else
         err = couldntGetRequiredComponent;
   }
   return err;
}

BROWSING THE NETWORK

Now that the conference is set up, we can place a "call" out onto the network to the broadcaster that the user wants to watch. We'll use the AppleTalk browser component to pick a registered broadcaster.

The BrowseName routine (Listing 2) opens the browser component and uses the MTBrowserBrowse component call to specify which kind of network entity to look for. In this case it's a MovieTalk entity registered on an AppleTalk network with the NBP type of "Multicaster"; this type identifies broadcasts from Caster and AMC. MTBrowserBrowse then presents users with the browser dialog, where they can "surf" the network and find the appropriate broadcaster. Some browsers (like the PowerTalk browser) can return multiple names in an MTNameList. We're only interested in the one AppleTalk broadcast picked by the user, so we pick off the first MTName from the MTNameList.

Listing 2. BrowseName

ComponentResult BrowseName(MTNamePtr name)
{
   MTNameListPtr         allNames = 0;
   ComponentResult       err;
   MTBrowserComponent    browser = nil;

   browser = OpenDefaultComponent(kMTBrowserType,
                kMTAppleTalkSubType);
   if (browser) {
      err = MTBrowserBrowse(browser, 0, nil, 
               (MTCString)"mtlkatlk\tMulticaster\x0D", 0, &allNames);
      CloseComponent(browser);
   }
   else
      err = couldntGetRequiredComponent;
   if ((allNames != 0) && (err == noErr)) {
      /* Copy the first name record; that's all we're interested */
      /* in. */
      *name = allNames->list[0];
      /* Dispose of the list of names. */
      DisposePtr((Ptr)allNames);
   }
   return err;
}

TUNING IN

CallMember (Listing 3) is the code needed to tell the conference component to place a call to the broadcaster. The calling routine passes in the MTName (obtained from BrowseName) and a pointer to the window in which the broadcast is to appear (and that window's size). The resize parameter will be used later to determine whether to resize the window automatically to the dimensions of the video being broadcast. CallMember returns a pointer to a new MemberRecord data structure, where the information about each broadcast-watching window is kept. The important conference component call here is MTConferenceCall, which is passed a reference to the conference component, an arbitrary name for the conference, and the MTName describing the party whose broadcast we want to watch.

Note that the conference component manages each independent connection to a broadcaster as a unique conference. That's just fine for our application, since the broadcast windows are really independent. In multiparty connections, however, conferences can be joined and then individual members can belong to the same conference. In that case the conference name parameter in MTConferenceCall ("Watcher" in Listing 3) may have more meaning and may be used to distinguish independent conferences. In our case, we give them all the same name.

Listing 3. CallMember

ComponentResult CallMember(MTName* name, WindowPtr wind, Rect* box, 
                           Boolean resize, MemberRecord** member) 
{
   MemberRecord*      mr;
   ComponentResult   err;

   /* Create a new member record. */
   err = NewMember(&mr);
   if (err == noErr) {
      mr->member = MTConferenceCall(gConference->confComponent, 
                            (MTCString)"Watcher", name);
      mr->box = *box;
      mr->window = wind;
      mr->resize = resize;
      if (member)
         *member = mr;
   }
   return err;
}

TURNING ON

Now that the conference call has been placed, we need to check the conference component periodically to find out about changes in the conference. Listing 4 shows the routine CheckConferenceEvents, which is intended to be called within the main event loop of the application. Each time through the loop, we call MTConferenceGetNextEvent. Most of the time this will return false, indicating that there are no new events. When some state in the conference has changed, it will return true, and we should then parse the event (with HandleConferenceEvent) to see what the correct response is.

Listing 4. CheckConferenceEvents

ComponentResult CheckConferenceEvents(void)
{
   MTConferenceEvent confEvent;
   ComponentResult   err; 
   
   if (MTConferenceGetNextEvent(gConference->confComponent,
          &confEvent))
      err = HandleConferenceEvent(&confEvent);
   return err;
}
The MTConferenceEvent data structure, also known as an event record, has several fields that we'll use in the following listings. The what field indicates the type of event; depending on this, HandleConferenceEvent (Listing 5) switches to the individual subroutines corresponding to each event. The surprise field, if not set to 0, contains a handle to data that's associated with the event and needs to be disposed of after use. The other fields, who, err, and bonus, contain references to the members, error codes, and event-specific data, respectively. See the documentation for details on the meanings of these fields for all event types.

Listing 5. HandleConferenceEvent

ComponentResult HandleConferenceEvent(MTConferenceEventPtr confEvent)
{
   ComponentResult   err = noErr;
   
   /* Like a user event handler, we switch on the different
      conference events. */
   switch (confEvent->what) {
      case mtConferenceReadyEvent:
         err = DoConfReady(confEvent);
         break;
      case mtMemberReadyEvent:
         err = DoMemberReady(confEvent);
         break;
      case mtMemberTerminatedEvent:
         err = DoMemberTerminated(confEvent);
         break;
      case mtMemberJoiningEvent:
         err = DoMemberJoining(confEvent);
         break;
      case mtPhoneRingingEvent:
         err = DoPhoneRinging(confEvent);
         break;
      case mtRefusedEvent:
      case mtFailedEvent:
         err = confEvent->err;
         break;
      default:      /* Ignore all others. */
         break;
      }

   /* If there's data associated with this event, free it. */
   if (confEvent->surprise)
      DisposeHandle(confEvent->surprise);
   return err;
}
After a call has been placed and a connection has been established with the remote side, an event of type mtMemberJoiningEvent is returned by the conference component. Upon receiving this event our application calls DoMemberJoining (Listing 6) and simply makes a record of this new member and adds it to our list of members. The conference component will continue to establish the connection and will notify us further when the connection has been completely brought up.

Listing 6. DoMemberJoining

struct MemberRecord {
   MTControllerComponent   controller;
   MTDirectorComponent     director;
   MTConferenceMember      member;
   WindowPtr               window;
   Boolean                 resize;
   Rect                    box;
   MemberRecord*           next;
};
...
ComponentResult DoMemberJoining(MTConferenceEventPtr confEvent)
{
   MemberRecord*      currMember;
   ComponentResult    err = noErr;

   err = NewMember(&currMember);
   if (err != noErr) {
      currMember->member = confEvent->who;
      AddMember(gConference, currMember);
   }
   return err;
}
Once the connection has been fully established, the conference component sends us an event of type mtMemberReadyEvent. Now we have a little more work to do. In this case, the application needs to create a controller and place that controller into a window for incoming media to be displayed. The conference component can do much of this work for you, including creating a controller (and its associated stream director) as well as creating a window and even handling user events for that window, with the MTConferenceNewPreparedController call. For many applications this method is perfectly adequate, but if you need more control over event handling and window management in your application, you'll want to do this work manually, as we do in Watcher and Caster. Use of MTConferenceNewPreparedController is demonstrated in the SeeWorld sample applications included on this issue's CD; check out the Rogues and Guardian examples in particular.

DoMemberReady (Listing 7) first checks to see if we can expect media to be sent by the new member. (If the member isn't sending media, there's no point in setting up a window.) If the member is sending media, we create a controller component and a stream director component, which are responsible for displaying the media data. After this, we call MTControllerNewAttachedController to connect the controller to the stream director and point it at a window for display. We then do one more thing to the controller before activating it in the conference: we set an action filter for it. The action filter is a callback routine that the controller calls whenever any important action happens within the controller. In our application, the only action that we care about is the resizing of the media data so that we can resize the window. The action filter routine is shown in Listing 8.

Listing 7. DoMemberReady

ComponentResult DoMemberReady(MTConferenceEventPtr confEvent)
{
   ComponentResult   err = noErr;
   MemberRecord*      currMember;
   Point               where = {0, 0};
   Boolean            aTrue = true;
   
   if (confEvent->bonus & mtReceiveMediaModeMask) {
      currMember = FindMember(gConference, confEvent->who);
      if (currMember == nil)
         return noErr;
      currMember->controller = 
         OpenDefaultComponent(kMTControllerType,
             kMTMovieTalkSubType);
      if (currMember->controller == 0)
         err = couldntGetRequiredComponent;
      if (err == noErr) {
         currMember->director = OpenDefaultComponent(
                           kMTSinkStreamDirectorType, kMTPlayerType);
         if (currMember->director == 0)
            err = couldntGetRequiredComponent;
      }
      if (err == noErr)
         err = MTControllerNewAttachedController(
                  currMember->controller, currMember->director,
                  currMember->window, where);
      if (err == noErr)
         err = MTControllerSetActionFilter(currMember->controller, 
                  actionFilterUPP, (long)currMember);
      if (err == noErr)
         err = MTConferenceActivateMember(gConference->confComponent,
                  confEvent->who, currMember->controller);
      if (err == noErr)
         err = MTControllerDoAction(currMember->controller, 
                  mtControllerActionPlay, &aTrue);
   }
   return err;
}

Listing 8. MyControllerActionFilter

pascal Boolean MyControllerActionFilter(MTControllerComponent mtc, 
                                       MTControllerActionType action, 
                                       void* params, long refCon) 
{
   void*       unused1 = params;
   long        unused2 = refCon;
   RgnHandle   controllerRgn;
   Boolean     handled = false;
   Rect        box;
   WindowPtr   controllerWindow = 
                       (WindowPtr)MTControllerGetControllerPort(mtc);
   
   switch (action) {
      case mtControllerActionControllerSizeChanged:
         /* Find out how big the controller is. */
         controllerRgn = MTControllerGetWindowRgn(mtc,
                                                   controllerWindow);
         /* Resize the window accordingly. */
         if (controllerRgn != nil) {
            box = (**controllerRgn).rgnBBox;
            DisposeRgn(controllerRgn);
            SizeWindow(controllerWindow, box.right, box.bottom,
                true);
         }
         break;
      default:
         break;
      }
   return handled;
}
Finally, DoMemberReady calls MTConferenceActivateMember to activate the member, and we pass MTConferenceActivateMember the newly created controller. Before exiting, we call MTControllerDoAction to tell the controller component to begin playing the incoming media as soon as it begins. (Controllers are by default in a paused state when they're created.)

DROPPING OUT

When the user has decided to close down the reception of the broadcast (say, by closing a broadcast window), the application calls CloseWatch (Listing 9). CloseWatch will find the member record corresponding to the conference member and obtain the conference token associated with that member. (Remember, each member is part of a unique conference, so the member has both a conference token and a unique ConferenceMember identifier.) Then we begin to terminate the conference by calling MTConferenceTerminate.

Listing 9. CloseWatch

ComponentResult CloseWatch(WindowPtr window) 
{
   ComponentResult       err = noErr;
   MTConferenceToken    theConference;
   MemberRecord*         theMember;
   
   theMember = FindMemberWindow(gConference, window);
   if (theMember == nil)
      err = paramErr;
   if (err == noErr) {
      theConference = MTConferenceGetMemberConference(
                        gConference->confComponent,
                        theMember->member);
      err = MTConferenceTerminate(gConference->confComponent, 
                                    theConference);
   }
   return err;
}
The conference isn't completely terminated until we receive an event of type mtMemberTerminatedEvent, which is handled by DoMemberTerminated (Listing 10). DoMemberTerminated is called when the conference connection for this member has been completely terminated, either by an MTConferenceTerminate call or by the remote side closing down. In response, we'll close down the controller and stream director components and the associated window, then free up our application's MemberRecord for this member.

Listing 10. DoMemberTerminated

ComponentResult DoMemberTerminated(MTConferenceEventPtr confEvent)
{
   MemberRecord*      member;
   ComponentResult   err;
   
   member = FindMember(gConference, confEvent->who);
   if (member == nil)
      return noErr;
   RemoveMember(gConference, member);
   if (member->controller)
      CloseComponent(member->controller);
   if (member->director)
      CloseComponent(member->director);
   if (member->window)
      CloseWindow(member->window);
   err = DisposeMemberRecord(member);
   return err;
}
That's it for the key QTC routines in Watcher. Check out the source code on the CD to see the entire package come together.

BROADCASTING WITH CASTER

Caster, the broadcasting side of this networked multimedia system, is similar to Watcher in many ways. It uses a conference component (see Figure 6) and processes conference events, but it handles the other side of the conference establishment: setting up and transmitting media and accepting incoming calls. In some ways, Caster is simpler: since it broadcasts to anybody who wants to tune in, it doesn't need to keep track of each member individually.

Figure 6. A QTC broadcaster and two watchers

SETTING UP THE SEQUENCE GRABBER

Probably the trickiest part of Caster is the code that sets up the sequence grabber to capture video and sound. The call MTConferenceNewPreparedController from the conference component could be used to set up the sequence grabber (as well as the controller and stream director) in many cases, but as mentioned earlier for Watcher, this call won't be adequate if you need more control.

In the SetupSequenceGrabber routine (Listing 11), we first create the sequence grabber component by calling OpenDefaultComponent. Once the component is initialized with SGInitialize, we create the individual sound and video channels. We can use other calls in the sequence grabber component API to adjust settings, like frame rate and compressor type. We also need to call SGSetChannelUsage to tell the controller that the channels can be used for preview and record and that they will play through during recording (seqGrabPreview + seqGrabRecord + seqGrabPlayDuringRecord).

Listing 11. SetupSequenceGrabber

ComponentResult SetupSequenceGrabber(
        SeqGrabComponent* sg, SGChannel* soundChannel,
        SGChannel* videoChannel)
{
   ComponentResult    err = noErr;
   SeqGrabComponent    grabber = nil;

   *soundChannel = nil;
   *videoChannel = nil;
   grabber = OpenDefaultComponent(SeqGrabComponentType, 0);
   if (grabber == nil)
      err = couldntGetRequiredComponent;
   else {
      err = SGInitialize(grabber);
      if (err == noErr) {
         err = SGNewChannel(grabber, SoundMediaType, soundChannel);
         if (err == noErr)
            SGSetChannelUsage(*soundChannel,
                seqGrabPreview + seqGrabRecord);
         err = SGNewChannel(grabber, VideoMediaType, videoChannel);
         if (err == noErr) {
             SGSetFrameRate(*videoChannel, 0);
            /* 'rpza' is the Apple Video Compressor. */
            SGSetVideoCompressorType(*videoChannel, 'rpza');
            SGSetChannelUsage(*videoChannel,
                seqGrabPreview + seqGrabRecord
                   + seqGrabPlayDuringRecord);
         }
         /* Reset in case we had a problem opening a channel */
         /* (e.g., there was no digitizer). */
         err = noErr;
      }
   }
   if (err != noErr) {
      if (grabber)
         CloseComponent(grabber);
      grabber = nil;
   }
   *sg = grabber;
   return err;
}

ATTACHING THE SEQUENCE GRABBER

Now that we have the sequence grabber created as a source for captured data, we need to hook it up to the stream director and controller and create a pipeline for the media, which will eventually be fed into the conference component and out onto the network. OpenCast (Listing 12) takes a sequence grabber and a window to display it in, creates a source stream director and controller, and configures them.

Listing 12. OpenCast

typedef struct {
   WindowPtr               window;
   SeqGrabComponent        sg;
   MTConferenceComponent   confComponent;
   MTControllerComponent   controller;
   MTDirectorComponent     director;
   Boolean                 casting;
   MTConferenceToken       conference;
} CastRecord;
...

ComponentResult OpenCast(WindowPtr window, SeqGrabComponent sg, CastRecord** cr) 
{
   ComponentResult   err = noErr;
   CastRecord*         newRecord = nil;
   Point               origin = {0,0};
   /* Specify the default window bounds for a 160-by-120 video window; add 16 to the height 
      to make space for the controller. */
   Rect               bounds = {0, 0, 120 + 16, 160};
   Boolean            aFalse = false;

   newRecord = (CastRecord*)NewPtrClear(sizeof(CastRecord));
   if (newRecord == nil)
      err = MemError();
   if (err == noErr) {
      newRecord->window = window;
      newRecord->sg = sg;   
      newRecord->director = OpenDefaultComponent(kMTSourceStreamDirectorType, kMTGrabberSubType);
      if (newRecord->director == nil)
         err = couldntGetRequiredComponent;
   }
   if (err == noErr) {
      newRecord->controller = OpenDefaultComponent(kMTControllerType, kMTMovieTalkSubType);
      if (newRecord->controller == nil)
         err = couldntGetRequiredComponent;
   }
   if (err == noErr)
      err = MTControllerSetActionFilter(newRecord->controller, actionFilterUPP, 0);
   if (err == noErr)
      err = MTDirectorSetMediaComponent(newRecord->director, sg);
   if (err == noErr)
      err = MTControllerNewAttachedController(newRecord->controller, newRecord->director, window,
                        origin);
   if (err == noErr)   
      err = MTControllerDoAction(newRecord->controller, mtControllerActionSetShowSnapshot, &aFalse);
   if (err == noErr)
      err = MTControllerSetControllerBoundsRect(newRecord->controller, &bounds);
   if (err == noErr)   
      *cr = newRecord;
   else
      CloseCast(newRecord);
   return err;
}
After the source stream director and controller are created, we attach a controller action filter routine (as we did before for Watcher) and connect the sequence grabber to the stream director with the MTDirectorSetMediaComponent call. The value of the source stream director subtype is the same as the value of the sequence grabber type, indicating that this source stream director has a sequence grabber as its source. We then call MTControllerNewAttachedController to attach the controller to the stream director; MTControllerDoAction with mtControllerActionSetShowSnapshot, passing in false to hide the snapshot button (not the default behavior); and finally MTControllerSetControllerBoundsRect to give the controller an initial bounds size.

STARTING TO BROADCAST

Now that we're ready to start broadcasting, we'll create the conference component and have it start listening for incoming calls from watchers, as shown in Listing 13. MTConferenceSetMode indicates to the controller that we'll want to send media (which we didn't want to do with Watcher) and that we expect to share a single director/controller source with multiple members of a conference. We won't actually attach the controller/director/sequence grabber chain to the conference component until somebody calls in.

Listing 13. StartCasting

ComponentResult StartCasting(CastRecord* cr, Str63 name)
{   
   MTCString63         cName;
   ComponentResult   err = noErr;
   
   PToCString(name, cName);   
   cr->confComponent =
       OpenDefaultComponent(kMTConferenceType, kMTMovieTalkSubType);
   if (cr->confComponent == nil)
      err = couldntGetRequiredComponent;
   if (err == noErr)
      err = MTConferenceSetMode(cr->confComponent,
                mtSendMediaModeMask + mtShareableModeMask);
   if (err == noErr)
      err = MTConferenceListen(cr->confComponent, cName, cName, 
                     (MTCString)"mtlkatlk\tMulticaster\x0D");
   if (err == noErr)
      cr->casting = true;
   return err;
}
Finally, we begin listening with the call to MTConferenceListen, passing it the C string indicating the transport, network, and configuration information. In this case the transport type is 'mtlk' for the MovieTalk protocol transport component, the network type is 'atlk' for AppleTalk, and the configuration string is "Multicaster"; the latter will be used by AppleTalk as an NBP type. This is the AppleTalk NBP type that the browser in Watcher looked for while browsing the network. (This is also the type that AMC uses, so we'll be able to watch Caster broadcasts with it, too.)

ANSWERING THE CALLS WHEN THEY COME IN

Once the conference component has been set up, Caster periodically checks it for conference events, just as Watcher does. Some of the behavior in response to these events is a little different, mainly because Caster is receiving incoming calls and sending media. Listing 14 shows the routines that get called in response to the following conference events: mtIncomingCallEvent, mtConferenceReadyEvent, mtMemberReadyEvent, and mtConferenceTerminatedEvent.

Listing 14. Routines for responding to conference events

ComponentResult DoIncomingCall(CastRecord* cr,
    MTConferenceEventPtr confEvent) 
{   
   return MTConferenceReply(cr->confComponent, confEvent->who, 0);
}

ComponentResult DoConferenceReady(CastRecord* cr,
    MTConferenceEventPtr confEvent) 
{
   ComponentResult   err = noErr;

   if (cr->conference == 0) {
      cr->conference = confEvent->who;
      err = MTConferenceActivateConference(cr->confComponent,
                cr->conference, cr->controller);
   }
   else
      err = MTConferenceMerge(cr->confComponent, cr->conference,
                confEvent->who);
   return err;
}

ComponentResult DoMemberReady(CastRecord* cr,
    MTConferenceEventPtr confEvent) 
{
   ComponentResult   err = noErr;

   err = MTConferenceActivateMember(cr->confComponent,
             confEvent->who, 0);
   if (err == noErr)
      err = MTConferenceDetachMember(cr->confComponent,
                confEvent->who);
   return err;
}

ComponentResult DoConferenceTerminated(CastRecord* cr,
    MTConferenceEventPtr confEvent) 
{
   ComponentResult   err = noErr;

   if (cr->conference == confEvent->who) {
      cr->conference = 0;
      MTControllerDoAction(cr->controller, mtControllerActionPlay,
          &aTrue);
   }
   return err;
}
In response to an mtIncomingCallEvent, the DoIncomingCall routine simply invokes the conference component's MTConferenceReply function to essentially answer the call immediately. A more complex version of this routine might check the caller's identity to determine whether the caller has permission to watch the broadcast. Caster will take all callers and reply immediately.

Upon receipt of the mtConferenceReadyEvent, passed when the conference has been fully established, we'll take one of two courses of action:

  • If this is the first incoming caller, and therefore the first conference, we'll save the conference token (in the conference event's who field) and activate the conference with the MTConferenceActivateConference function. This is where we connect up the controller/stream director/sequence grabber configuration by passing in a reference to the source controller.

  • If this is the second or later watcher tuning in, this watcher will join as a new member in a new conference. We'll call MTConferenceMerge to merge this new conference with the original conference so that the new member is sent the media.
Now that the conference is set up, we should expect to receive an event of type mtMemberReadyEvent. Here we simply activate the member to start receiving the broadcast. Then we call a special function designed to help us take advantage of multicast network services if available, MTConferenceDetachMember. This function will "detach" the member from a direct point-to-point connection and will rely on multicast services to get the member its data. In this case the receiving side and Caster can't send reliable messages to each other, but for our application that's just fine; we'd rather minimize the network traffic.

Finally, when a watcher disconnects, for whatever reason, we're notified with an mtConferenceTerminatedEvent and call DoConferenceTerminated. If this is the first conference, we forget about it by resetting our conference token to 0. (We also get termination events for conferences that were merged, so we just ignore those.) When the connection is torn down the media is stopped by the stream director, so to continue the preview for the user we tell the controller to start playing again with the MTControllerDoAction function.

ADJUSTING THE PICTURE

Typically, developers want to enable users to change media settings of the sequence grabber when it's connected to the other components and even when we're sending to a conference. In Listing 15, we use the sequence grabber SGSettingsDialog function to present users with a configuration dialog so that they can change the video or audio settings. It's not really safe to talk to the sequence grabber directly without warning the other parts of the connection that the media formats will change.

Listing 15. CastChannelSettings

ComponentResult CastChannelSettings(CastRecord* cr,
    SGChannel channel)
{
   ComponentResult   err = noErr;
   
   err = MTControllerChangedStreams(cr->controller, false);
   if (err == noErr) {
      err = SGSettingsDialog(cr->sg, channel, 0, nil, 0, nil, nil);
      MTControllerChangedStreams(cr->controller, true);
   }
   return err;
}
We surround the call to SGSettingsDialog with calls to the controller function MTControllerChangedStreams. The second parameter is a Boolean that indicates whether we've finished changing the streams. Calling MTControllerChangedStreams with this parameter set to false pauses the media in the connection and makes it safe to change the setting. Then after the sequence grabber has been adjusted, we call MTControllerChangedStreams again with this parameter set to true to indicate that we're done. This in turn starts the process of "renegotiating" the media formats across the connection safely.

CONNECTING FURTHER

There's a wealth of documentation available to help you add QTC support to your new and existing applications.

Inside Macintosh: QuickTime Conferencing can be found on this issue's CD, documenting the API for all of the QTC components as well as the MovieTalk protocol. The rest of the QTC documentation, including more sample code, human interface notes, and documentation on AppleTalk Multicast, can be found on the Mac OS SDK edition of the Developer CD Series. To learn about the intricacies of the sequence grabber and other media- and component-related topics, check out Inside Macintosh: QuickTime and Inside Macintosh: QuickTime Components.

Come visit us on the World Wide Web at http://qtc.quicktime.apple.com/; you'll find abundant QTC information there, including developer documentation and free software. To share your ideas about uses for QTC, you can reach the QTC team at movietalk@applelink.apple.com (AppleLink MOVIETALK). To get the licensing terms for QTC, contact Apple's Software Licensing department at sw.license@applelink.apple.com (AppleLink SW.LICENSE) or (512)919-2645, or write to Apple Computer, Inc., 2420 Ridgepoint Drive, M/S 198-SWL, Austin, TX 78754.

I hope that I've been able to give you an idea of what QuickTime Conferencing is all about and how to get started using this exciting new technology. No longer just the stuff of science fiction, videophone and other multimedia connections can be part of the Macintosh experience for everyone.

Thanks to our technical reviewers Eric Carlson, Brian Cox, Godfrey DiGiorgi, Kevin Gong, Eric Hoffert, and Guy Riddle.

DEAN BLACKKETTER (dean@artemis.com) used to work for Apple in the Advanced Technology Group. He now has a gig with Artemis Research working on "the next big thing." He plays in San Francisco with his wife, their cat, and the scary elf who lives on top of the fridge.

 

Community Search:
MacTech Search:

Software Updates via MacUpdate

Latest Forum Discussions

See All

Challenge those pesky wyverns to a dance...
After recently having you do battle against your foes by wildly flailing Hello Kitty and friends at them, GungHo Online has whipped out another surprising collaboration for Puzzle & Dragons. It is now time to beat your opponents by cha-cha... | Read more »
Pack a magnifying glass and practice you...
Somehow it has already been a year since Torchlight: Infinite launched, and XD Games is celebrating by blending in what sounds like a truly fantastic new update. Fans of Cthulhu rejoice, as Whispering Mist brings some horror elements, and tests... | Read more »
Summon your guild and prepare for war in...
Netmarble is making some pretty big moves with their latest update for Seven Knights Idle Adventure, with a bunch of interesting additions. Two new heroes enter the battle, there are events and bosses abound, and perhaps most interesting, a huge... | Read more »
Make the passage of time your plaything...
While some of us are still waiting for a chance to get our hands on Ash Prime - yes, don’t remind me I could currently buy him this month I’m barely hanging on - Digital Extremes has announced its next anticipated Prime Form for Warframe. Starting... | Read more »
If you can find it and fit through the d...
The holy trinity of amazing company names have come together, to release their equally amazing and adorable mobile game, Hamster Inn. Published by HyperBeard Games, and co-developed by Mum Not Proud and Little Sasquatch Studios, it's time to... | Read more »
Amikin Survival opens for pre-orders on...
Join me on the wonderful trip down the inspiration rabbit hole; much as Palworld seemingly “borrowed” many aspects from the hit Pokemon franchise, it is time for the heavily armed animal survival to also spawn some illegitimate children as Helio... | Read more »
PUBG Mobile teams up with global phenome...
Since launching in 2019, SpyxFamily has exploded to damn near catastrophic popularity, so it was only a matter of time before a mobile game snapped up a collaboration. Enter PUBG Mobile. Until May 12th, players will be able to collect a host of... | Read more »
Embark into the frozen tundra of certain...
Chucklefish, developers of hit action-adventure sandbox game Starbound and owner of one of the cutest logos in gaming, has released their roguelike deck-builder Wildfrost. Created alongside developers Gaziter and Deadpan Games, Wildfrost will... | Read more »
MoreFun Studios has announced Season 4,...
Tension has escalated in the ever-volatile world of Arena Breakout, as your old pal Randall Fisher and bosses Fred and Perrero continue to lob insults and explosives at each other, bringing us to a new phase of warfare. Season 4, Into The Fog of... | Read more »
Top Mobile Game Discounts
Every day, we pick out a curated list of the best mobile discounts on the App Store and post them here. This list won't be comprehensive, but it every game on it is recommended. Feel free to check out the coverage we did on them in the links below... | Read more »

Price Scanner via MacPrices.net

Free iPhone 15 plus Unlimited service for $60...
Boost Infinite, part of MVNO Boost Mobile using AT&T and T-Mobile’s networks, is offering a free 128GB iPhone 15 for $60 per month including their Unlimited service plan (30GB of premium data).... Read more
$300 off any new iPhone with service at Red P...
Red Pocket Mobile has new Apple iPhones on sale for $300 off MSRP when you switch and open up a new line of service. Red Pocket Mobile is a nationwide MVNO using all the major wireless carrier... Read more
Clearance 13-inch M1 MacBook Airs available a...
Apple has clearance 13″ M1 MacBook Airs, Certified Refurbished, available for $759 for 8-Core CPU/7-Core GPU/256GB models and $929 for 8-Core CPU/8-Core GPU/512GB models. Apple’s one-year warranty is... Read more
Updated Apple MacBook Price Trackers
Our Apple award-winning MacBook Price Trackers are continually updated with the latest information on prices, bundles, and availability for 16″ and 14″ MacBook Pros along with 13″ and 15″ MacBook... Read more
Every model of Apple’s 13-inch M3 MacBook Air...
Best Buy has Apple 13″ MacBook Airs with M3 CPUs in stock and on sale today for $100 off MSRP. Prices start at $999. Their prices are the lowest currently available for new 13″ M3 MacBook Airs among... Read more
Sunday Sale: Apple iPad Magic Keyboards for 1...
Walmart has Apple Magic Keyboards for 12.9″ iPad Pros, in Black, on sale for $150 off MSRP on their online store. Sale price for online orders only, in-store price may vary. Order online and choose... Read more
Apple Watch Ultra 2 now available at Apple fo...
Apple has, for the first time, begun offering Certified Refurbished Apple Watch Ultra 2 models in their online store for $679, or $120 off MSRP. Each Watch includes Apple’s standard one-year warranty... Read more
AT&T has the iPhone 14 on sale for only $...
AT&T has the 128GB Apple iPhone 14 available for only $5.99 per month for new and existing customers when you activate unlimited service and use AT&T’s 36 month installment plan. The fine... Read more
Amazon is offering a $100 discount on every M...
Amazon is offering a $100 instant discount on each configuration of Apple’s new 13″ M3 MacBook Air, in Midnight, this weekend. These are the lowest prices currently available for new 13″ M3 MacBook... Read more
You can save $300-$480 on a 14-inch M3 Pro/Ma...
Apple has 14″ M3 Pro and M3 Max MacBook Pros in stock today and available, Certified Refurbished, starting at $1699 and ranging up to $480 off MSRP. Each model features a new outer case, shipping is... Read more

Jobs Board

*Apple* Systems Administrator - JAMF - Activ...
…**Public Trust/Other Required:** None **Job Family:** Systems Administration **Skills:** Apple Platforms,Computer Servers,Jamf Pro **Experience:** 3 + years of Read more
IT Systems Engineer ( *Apple* Platforms) - S...
IT Systems Engineer ( Apple Platforms) at SpaceX Hawthorne, CA SpaceX was founded under the belief that a future where humanity is out exploring the stars is Read more
Nurse Anesthetist - *Apple* Hill Surgery Ce...
Nurse Anesthetist - Apple Hill Surgery Center Location: WellSpan Medical Group, York, PA Schedule: Full Time Sign-On Bonus Eligible Remote/Hybrid Regular Apply Now Read more
Housekeeper, *Apple* Valley Village - Cassi...
Apple Valley Village Health Care Center, a senior care campus, is hiring a Part-Time Housekeeper to join our team! We will train you for this position! In this role, Read more
Sublease Associate Optometrist- *Apple* Val...
Sublease Associate Optometrist- Apple Valley, CA- Target Optical Date: Apr 20, 2024 Brand: Target Optical Location: Apple Valley, CA, US, 92307 **Requisition Read more
All contents are Copyright 1984-2011 by Xplain Corporation. All rights reserved. Theme designed by Icreon.