Tasks

This chapter describes common QTSS tasks:

Building the Streaming Server

This section describes the Streaming Server build and install process for Mac OS X, POSIX, and Windows platforms.

Mac OS X

Use the Buildit script to build the Streaming Server for Mac OS X. Use the following command line options: StreamingServer.pbroj -target DSS. As they are built, the binaries are left in the build directory.

The command BuildOSXInstallerPkg dss creates a file named DarwinStreamingServer.pkg.

POSIX

Use the Buildit script to build the Streaming Server on POSIX platforms. Binaries are left in the source directories. To create the installer, use the buildtarball script, which creates an install directory with Install script and tar file.

Windows

Use the WindowsNTSupport/StreamingServer.dsw script to build the Streaming Server on Windows platforms. Batch build all. Binaries are left in the Debug and Release directory. The WindowsNTSupport/makezip.bat script creates an install directory with an Install.bat file.

Building a QuickTime Streaming Server Module

You can add a QTSS module to the QuickTime Streaming Server by compiling the code directly into the server itself or by building a module as a separate code fragment that is loaded when the server starts up.

Whether compiled into the server or built as a separate module, the code for the module is the same. The only difference is the way in which the code is compiled.

Compiling a QTSS Module into the Server

If you have the source code for the QuickTime Streaming Server, you can compile your module into the server.

To compile your code into the server, locate the function QTSServer::LoadCompiledInModules in QTSServer.cpp and add to it the following lines

QTSSModule* myModule = new QTSSModule("__XYZ__");
(void)myModule->Initialize(&sCallbacks, &__XYZMAIN__);
(void)AddModule(myModule);

where XYZ is the name of your module and XYZMAIN is your module’s main routine.

Some platforms require that each module use unique function names. To prevent name conflicts when you compile a module into the server, make your functions static.

Modules that are compiled into the server are known as static modules.

Building a QTSS Module as a Code Fragment

To have the server load at runtime a QTSS module that is a code fragment, follow these steps:

  1. Compile the source for your module as a dynamic shared library for the platform you are targeting. For Mac OS X, the project type must be loadable bundle.

  2. Link the resulting file against the QTSS API stub library for the platforms you are targeting.

  3. Place the resulting file in the /Library/QuickTimeStreaming/Modules directory (Mac OS X), /usr/local/sbin/StreamingServerModules (Darwin platforms), and c:\Program Files\Darwin StreamingServer\QTSSModules. The server will load your module the next time it restarts.

Some platforms require that each module use unique function names. To prevent name conflicts when the server loads your module, strip the symbols from your module before you have the server load it.

Debugging

Several server preferences in the streamingserver.xml file are available for enabling the generation of debugging information, which is printed on the terminal screen. The following sections provide information on debugging:

RTSP and RTP Debugging

To enable the display of RTSP and RTP informati on the terminal screen, modify the RTSP_debug_printfs preference in the streamingserver.xml file and restart the server:

<PREF NAME="RTSP_debug_printfs" TYPE="BOOL16" >true</PREF>

To enable the display of packet header information, modify the “enable_packet_header_printfs” preference in the streamingserver.xml file:

<PREF NAME="enable_packet_header_printfs" TYPE="BOOL16" >true</PREF>

Then specify which packet headers to display by modifying the “packet_header_printf_options” preference. The following example enables the display of all packet headers:

<PREF NAME="packet_header_printf_options" >rtp;rr;sr;app;ack;</PREF>

In the previous example, rtp enables the display of RTP packet headers, rr enables the display of RTCP receiver reports, sr enables the display of RTCP sender reports, app enables the display of RTCP application packets, and ack enables the display of Reliable UDP RTP acknowledgement packets.

After enabling RTSP and RTP debugging, restart the Streaming Server in debug mode using this command:

QuickTimeStreamingServer -d

When you connect a client, debug information is displayed on the terminal screen.

Source File Debugging Support

You can enable debugging in specific source files. For example, in the file CommonUtilitiesLib/Task.h, make the following change:

#define TASK_DEBUG 1

Rebuild and start the Streaming Server in debug mode:

QuickTimeStreamingServer -d

Here is some sample output:

Task::Signal enque task TaskName=RTSPSession ...
TaskThread::Entry run task TaskName=RTSPSession ...
TaskThread::Entry insert task TaskName=RTSPSession ...
TaskThread::Entry run task TaskName=RTSPSession ...
TaskThread::WaitForTask found timer task TaskName=QTSSAccessLog ...
TaskThread::Entry run task TaskName=QTSSAccessLog ...

You can also enable debugging in CommonUtilitiesLib/OSFileSource.cpp:

#define FILE_SOURCE_DEBUG 1

Here is some sample output:

OSFileSource::SetLog=/Library/QuickTimeStreaming/Movies/sample_100kbit.mov
FileMap::AllocateBufferMap shared buffers
OSFileSource::ReadFromCache inPosition =272 ...
OSFileSource::ReadFromCache inPosition =276 ...
OSFileSource::ReadFromCache inPosition =280 ...
...
OSFileSource::ReadFromCache inPosition =80667

Working with Attributes

QTSS objects consist of attributes that are used to store data. Every attribute has a name, an attribute ID, a data type, and permissions for reading and writing the attribute’s value. There are two attribute types:

Getting Attribute Values

Modules use attributes stored in objects to exchange information with the server, so they frequently get attribute values. Three callback routines get attribute values:

  • QTSS_GetValue, which copies the attribute value into a buffer provided by the module. This callback can be used to get the value of any attribute, but it is not as efficient as QTSS_GetValuePtr.

  • QTSS_GetValueAsString, which copies the attribute value as a string into a buffer provided by the module. This callback can be used to get the value of any attribute. This is the least efficient way to get the value of an attribute

  • QTSS_GetValuePtr, which returns a pointer to the server’s internal copy of the attribute value. This is the most efficient way to get the value of preemptive safe attributes. It can also be used to get the value of non-preemptive safe attributes, but the object must first be locked and must be unlocked after QTSS_GetValuePtr is called. When getting the value of a single non-preemptive-safe attribute, calling QTSS_GetValue may be more efficient than locking the object, calling QTSS_GetValuePtr and unlocking the object.

The sample code in Listing 2-1 calls QTSS_GetValue to get the value of the qtssRTPSvrCurConn attribute, which is not preemptive safe, from the QTSS_ServerObject object.

Listing 2-1  Getting the value of an attribute by calling QTSS_GetValue

UInt32 MyGetNumCurrentConnections(QTSS_ServerObject inServerObject)
{
    // qtssRTPSvrCurConn is a UInt32, so provide a UInt32 for the result.
    UInt32 theNumConnections = 0;
    // Pass in the size of the attribute value.
    UInt32 theLength = sizeof(theNumConnections);
    // Retrieve the value.
    QTSS_Error theErr = QTSS_GetValue(inServerObject, qtssRTPSvrCurConn, 0,
        &theNumConnections, &theLength);
    // Check for errors. If the length is not what was expected, return 0.
    if ((theErr != QTSS_NoErr) || (theLength != sizeof(theNumConnections))
        return 0;
    return theNumConnections;
}

The sample code in Listing 2-2 calls QTSS_GetValuePtr, which is the preferred way to get the value of preemptive-safe attributes. In this example, value of the qtssRTSPReqMethod attribute is obtained from the object QTSS_RTSPRequestObject.

Listing 2-2  Getting the value of an attribute by calling QTSS_GetValuePtr

QTSS_RTSPMethod MyGetRTSPRequestMethod(QTSS_RTSPRequestObject inRTSPRequestObject)
{
    QTSS_RTSPMethod* theMethod = NULL;
    UInt32 theLen = 0;
 
QTSS_Error theErr = QTSS_GetValuePtr(inRTSPRequestObject, qtssRTSPReqMethod, 0,
    (void**)&theMethod, &theLen);
if ((theErr != QTSS_NoErr) || (theLen != sizeof(QTSS_RTSPMethod))
    return -1;  // Return a -1 if there is an error, which is not a valid
                // QTSS_RTSPMethod index
else
    return *theMethod;
}

You can obtain the value any attribute by calling QTSS_GetValueAsString, which gets the attribute’s value as a C string. Calling QTSS_GetValueAsString is convenient when you don’t know the type of data the attribute contains. In Listing 2-3, the value of the qtssRTPSvrCurConn attribute is obtained as a string from the QTSS_ServerObject.

Listing 2-3  Getting the value of an attribute by calling QTSS_GetValueAsString

void MyPrintNumCurrentConnections(QTSS_ServerObject inServerObject)
{
    // Provide a string pointer for the result
    char* theCurConnString = NULL;
    // Retrieve the value as a string.
    QTSS_Error theErr = QTSS_GetValueAsString(inServerObject, qtssRTPSvrCurConn, 0,  &theCurConnString);
    if (theErr != QTSS_NoErr) return;
    // Print out the result. Because the value was returned as a string, use
    // %s in the printf format.
    ::printf("Number of currently connected clients: %s\n", theCurConnString);
    // QTSS_GetValueAsString allocates memory, so reclaim the memory by calling  QTSS_Delete.
    QTSS_Delete(theCurConnString);
}

Setting Attribute Values

Two QTSS callback routines are available for setting the value of an attribute: QTSS_SetValue and QTSS_SetValuePtr.

The sample code in Listing 2-4 would be found handling the Route role. It calls QTSS_GetValuePtr to get the value of the qtssRTSPReqFilePath. If the path matches a certain string, the function sets a new request root directory by calling QTSS_SetValue to set the qtssRTSPReqRootDir attribute to a new path.

Listing 2-4  Setting the value of an attribute by calling QTSS_SetValue

// First get the file path for this request using QTSS_GetValuePtr
char* theFilePath = NULL;
UInt32 theFilePathLen = 0;
QTSS_Error theErr = QTSS_GetValuePtr(inParams->inRTSPRequest, qtssRTSPReqFilePath,  0, &theFilePath,
                &theFilePathLen);
 
// Check for any errors
if (theErr != QTSS_NoErr) return;
// See if this path is a match. If it is, use QTSS_SetValue to set the root directory  for this request.
if ((theFilePathLen == sStaticFilePathLen) &&
                    (::strncmp(theFilePath, sStaticFilePath, theFilePathLen) ==  0))
{
    theErr = QTS_SetValue(inParams->inRTSPRequest, qtssRTSPReqRootDir, 0,  sNewRootDirString,
            sNewRootDirStringLen);
    if (theErr != QTSS_NoErr) return;
}

Listing 2-5 demonstrates the use of the QTSS_SetValuePtr callback. The QTSS_SetValuePtr callback associates an attribute with the value of a module’s variable. This code sample modifies the QTSS_ServerObject object nonatomically, so it calls QTSS_LockObject to prevent other threads from accessing the attributes of the QTSS_ServerObject before the value has been set.

Then the code sample calls QTSS_CreateObjectValue to create a QTSS_ConnectedUserObject object as the value of the qtssSvrConnectedUsers attribute of the QTSS_ServerObject object. Then the code sample calls QTSS_SetValuePtr to set the value of the qtssConnectionBytesSent attribute of the QTSS_ConnectedUserObject object to the module’s fBytesSent variable. Thereafter, when any module gets the value of the qtssConnectionBytesSent attribute, it will get the current value of the module’s fBytesSent variable.

After calling QTSS_SetValuePtr, the code sample calls QTSS_UnlockObject to unlock the QTSS_ServerObject object.

Listing 2-5  Setting the value of an attribute by calling QTSS_SetValuePtr

UInt32 index;
QTSS_LockObject(sServer);
 
QTSS_CreateObjectValue(sServer, qtssSvrConnectedUsers,  qtssConnectedUserTypeObject, &index, &fQTSSObject);
 
QTSS_CreateObjectValue(sServer, qtssSvrConnectedUsers,  qtssConnectedUserObjectType, &index, &fQTSSObject);
 
QTSS_SetValuePtr(fQTSSObject, qtssConnectionBytesSent, &fBytesSent,  sizeof(fBytesSent));
 
QTSS_UnlockObject(sServer);

Adding Attributes

Any module can add an attribute to a QTSS object type by calling the QTSS_AddStaticAttribute callback routine from its Register role. Modules can also call QTSS_AddInstanceAttribute from any role to add an attribute to an instance of an object.

Once added, the new attribute is included in every object of that type that the server creates and its value can be set and obtained by calling that same callback routines that set and obtain the value of the server’s built-in attributes: QTSS_SetValue, QTSS_SetValuePtr, QTSS_GetValue, and QTSS_GetValuePtr.

The sample code in Listing 2-6 calls QTSS_AddStaticAttribute to add an attribute to the object QTSS_ClientSessionObject.

Listing 2-6  Adding a static attribute

QTSS_Error MyRegisterRoleFunction()
{
    // Add the static attribute. The third parameter is always NULL.
    QTSS_Error theErr = QTSS_AddStaticAttribute(qtssClientSessionObjectType,
                            "MySampleAttribute", NULL, qtssAttrDataTypeUInt32);
    // Retrieve the ID for this attribute. This ID can be passed into QTSS_GetValue,
    // QTSS_SetValue, and QTSS_GetValuePtr.
    QTSS_AttributeID theID;
    theErr = QTSS_IDForAttr(qtssClientSessionObjectType, MySampleAttribute",  &theID);
    // Store the attribute ID in a global for later use. Attribute IDs do not
    // change while the server is running.
    gMyExampleAttrID = theID;
}

Using Files

QTSS supports file system modules so that QTSS can transparently and easily work with custom file systems. For example, a QTSS file system module can allow a QTSS module to read a custom networked file system or a custom database. Support for reading files consists of the following:

Reading Files Using Callback Routines

In QTSS, a file is represented by a QTSS stream, so you can use existing QTSS stream callback routines to read files. The callback routines that are available for working with files are:

  • QTSS_OpenFileObject, which is called to open a file in the local operating system. This call is one of two callback routines that is only used when working with files.

  • QTSS_CloseFileObject, which is called to close a file that was opened by a previous call to QTSS_OpenFileObject. This call is one of two callback routines that is only used when working with files.

  • QTSS_Read, which is called to read data from a file object’s stream that was created by a previous call to QTSS_OpenFileObject.

  • QTSS_Seek, which is called to set the current position of a file object’s stream.

  • QTSS_Advise, which is called to tell a file system module that a specified section of one of its streams will be read soon.

  • QTSS_RequestEvent, which is called to tell a file system module that the calling module wants to be notified when one of the events in the specified event mask occurs. The events are when a stream becomes readable and when a stream becomes writable.

In QTSS, a file is QTSS_Object that has its own object type, QTSS_FileObject, that allows you to use standard QTSS callbacks (QTSS_GetValue, QTSS_GetValueAsString, and QTSS_GetValuePtr) to get meta information about a file, such as its length and modification date. You can use standard QTSS callbacks to store any amount of file system meta information with the file object. For example, a module working with a POSIX file system would want to add an attribute to the file object that stores the POSIX file system descriptor. A file object also has a QTSS stream reference that can be used when calling QTSS stream routines that work with files, such as QTSS_Read.

The sample code in Listing 3-7 shows how to open a file, determine the file’s length, read the entire file, close the file, and return the data it contains.

Listing 2-7  Reading a file

QTSS_Error ReadEntireFile(char* inPath, void** outData, UInt32* outDataLen)
{
 
    QTSS_Object theFileObject = NULL;
    QTSS_Error theErr = QTSS_OpenFileObject(inPath, qtssOpenFileNoFlags,  &theFileObject);
    if (theErr != QTSS_NoErr)
        return theErr; // The file wasn't found or it couldn't be opened.
 
    // The file is open. Find out how long it is.
    UInt64* theLength = NULL;
    UInt32 theParamLen = 0;
    theErr = QTSS_GetValuePtr(theFileObject, qtssFlObjLength, 0,  (void**)&theLength, &theParamLen);
 
    if (theErr != QTSS_NoErr)
        return theErr;
    if (theParamLen != sizeof(UInt64))
        return QTSS_RequestFailed;;
 
    // Allocate memory for the file data.
    *outData = new char[*theLength + 1];
    *outDataLen = *theLength;
 
    // Read the data
    UInt32 recvLen = 0;
    theErr = QTSS_Read(theFileObject, *outData, *outDataLen, &recvLen);
 
    if ((theErr != QTSS_NoErr) || (recvLen != *outDataLen))
    {
        delete *outData;
        return theErr;
    }
 
    // Close the file.
    (void)QTSS_CloseFileObject(theFileObject);
}

Implementing a QTSS File System Module

A file system module provides a way for QTSS modules to read files in a specific file system regardless of that file system’s type. Typically, a file system module handles a subset of paths in a file system, but it may handle all paths on the system. If a file system module handles only a certain subset of paths, it usually handles all paths inside a certain root path. For example, a module handling files stored in a certain database may only respond to paths that begin with /Local/database_root/.

Implementing a QTSS file system module begins with registering for one of the following roles:

  • Open File Preprocess role, which the server calls in response to a module (or the server) that calls the QTSS_OpenFileObject callback routine to open a file. If the module does not handle files of the specified type, the module immediately returns QTSS_FileNotFound. If the module handles the files of the specified type, it opens the file, updates a file object provided by the server and returns QTSS_NoErr. If an error occurs during this setup period, the module returns QTSS_RequestFailed. Once the module returns QTSS_NoErr, it should be prepared to handle the Advise File, Read File, Request Event File and Close File roles for the opened file. The server calls each module registered in the Open File Preprocess role until one of the called modules returns QTSS_NoErr or QTSS_RequestFailed.

  • Open File role, which the server calls in response to a module (or the server) that calls the QTSS_OpenFileObject callback routine for which all modules handling the Open File Preprocess role return QTSS_FileNotFound. Only one module can register for the Open File role. Like modules called for the Open File Preprocess role, the module called for the Open File role must determine whether it can handle the specified file. It it can, it opens the file, updates the file object provided by the server and returns QTSS_NoErr. If an error occurs during the setup process or if the module cannot handle the specified file, the module returns QTSS_RequestFailed or QTSS_FileNotFound, respectively.

A file system module should register in the Open File Preprocess role if it handles a subset of files available on the system. For instance, a file system module that serves files out of a database may only handle files rooted at a certain path. All other paths should fall through to other modules that handle other paths.

A file system module should register in the Open File role if it implements the default file system on a system. For instance, on a UNIX system the module handling the Open File Role would probably provide an interface between the server and the standard POSIX file system.

Once a module returns QTSS_NoErr from either the Open File Role or the Open File Preprocess role, it is responsible for the newly opened file. It should be prepared to handle the following roles on behalf of that file:

  • Advise File role, which is called in response to a module (or the server) calling the QTSS_Advise callback for a file object. The QTSS_Advise callback is made to inform the file system module that a specific region of the file will be needed soon.

  • Read File role, which is called in response to a module (or the server) calling the QTSS_Read callback for a file object. It is the responsibility of a file system module handling this role to make a best-effort attempt to fill the buffer provided by the caller with the appropriate file data.

  • Request Event File role, which is called in response to a module (or the server) calling the QTSS_RequestEvent callback on a file object.

  • Close File role, which is called in response to a module (or the server) calling the QTSS_Close callback on a file object. The module should clean up any file-system and module-specific data structures for this file. This role is always the last role a file system module will be invoked in for a given file object.

File System Module Roles

This section describes the file system module roles. The roles are:

Open File Preprocess Role

The server calls the Open File Preprocess role in response to a module that calls the QTSS_OpenFileObject callback routine to open a file. It is the responsibility of a module handling this role to determine whether it handles the type of file specified to be opened. If it does and if the file exists, the module opens the file, updates the file object provided by the server, and returns QTSS_NoErr.

When called, an Open File Preprocess role receives a QTSS_OpenFile_Params structure, which is defined as follows:

typedef struct
{
    char*               inPath;
    QTSS_OpenFileFlags  inFlags;
    QTSS_Object         inFileObject;
} QTSS_OpenFile_Params;s
inPath

A pointer to a null-terminated C string containing the full path to the file that is to be opened.

inFlags

Open flags specifying whether the module that called QTSS_OpenFileObject can handle asynchronous read operations (qtssOpenFileAsync) or expects to read the file in order from beginning to end (qtssOpenFileReadAhead).

inFileObject

A QTSS object that the module updates if it can open the file specified by inPath.

If the file is a file the module handles, the module should do whatever work is necessary to open and set up the file. It can use inFileObject to store any module-specific information for that file. In addition, the module should set the value of the file object’s qtssFlObjLenth and qtssFlObjModDate attributes.

If the file is a file the module handles but an error occurs while attempting to set up the file, the module should return QTSS_RequestFailed.

If every module registered for the Open File Preprocess role returns QTSS_FileNotFound, the server calls the one module that is registered in the Open File role.

A module that wants to be called in the Open File Preprocess role must in its Register role call QTSS_AddRole and specify QTSS_OpenFilePreprocess_Role as the role. Modules that register for this role must also handle the following roles, but they do not need to explicitly register for them: Advise File, Read File, Request Event File, and Close File.

Open File Role

The server calls the module registered for the Open File role when all modules registered for the Open File Preprocess role have been called and have returned QTSS_FileNotFound. Only one module can be registered for the Open File role, and that module is the first module that registers for this role when QTSS starts up.

Like modules called for the Open File Preprocess role, it is the responsibility of a module handling the Open File role to determine whether it handles the type of file specified to be opened. If it does and if the file exists, the module opens the file, updates the file object provided by the server, and returns QTSS_NoErr.

When called, the module receives a QTSS_OpenFile_Params structure, which is defined as follows:

typedef struct
{
    char*               inPath;
    QTSS_OpenFileFlags  inFlags;
    QTSS_Object         inFileObject;
} QTSS_OpenFile_Params;
inPath

A pointer to a null-terminated C string containing the full path to the file that is to be opened.

inFlags

Open flags specifying whether the module that called QTSS_OpenFileObject can handle asynchronous read operations (qtssOpenFileAsync) or expects to read the file in order from beginning to end (qtssOpenFileReadAhead).

inFileObject

A QTSS object that the module updates if it can open the file specified by inPath.

If the file is a file the module handles, the module should do whatever work is necessary to open and set up the file. It can use inFileObject to store any module-specific information for that file. In addition, the module should set the value of the file object’s qtssFlObjLength and qtssFlObjModDate attributes.

If the file is a file the module handles but an error occurs while attempting to set up the file, the module should return QTSS_RequestFailed.

A module that wants to be called in the Open File role must in its Register role call QTSS_AddRole and specify QTSS_OpenFile_Role as the role. Modules that register for this role must also handle the following roles, but they do not need to explicitly register for them: Advise File, Read File, Request Event File, and Close File.

Advise File Role

The server calls modules for the Advise File role in response to a module (or the server) calling the QTSS_Advise callback routine for a file object in order to inform the file system module that the calling module will soon read the specified section of the file.

When called, an Advise File role receives a QTSS_AdviseFile_Params structure, which is defined as follows:

typedef struct
{
    QTSS_Object         inFileObject;
    UInt64              inPosition;
    UInt32              inSize;
} QTSS_AdviseFile_Params;
inFileObject

The file object for the opened file. The file system module uses the file object to determine the file for which the QTSS_Advise callback routine was called.

inPosition

The offset in bytes from the beginning of the file that represents the beginning of the section that is soon to be read.

inSize

The number of bytes that are soon to be read.

The file system module is not required to do anything while handling this role, but it may take this opportunity to read the specified section of the file.

File system modules do not need to explicitly register for this role.

Modules should always return QTSS_NoErr when they finish handling this role.

Read File Role

The server calls modules for the Read File role in response to a module (or the server) calling the QTSS_Read callback routine for a file object in order to read the specified file.

When called, a Read File role receives a QTSS_ReadFile_Params structure, which is defined as follows:

typedef struct
{   QTSS_Object inFileObject;
    UInt64      inFilePosition;
    void*       ioBuffer;
    UInt32      inBufLen;
    UInt32*     outLenRead;
} QTSS_ReadFile_Params;
inFileObject

The file object for the file that is to be read. The file system module uses the file object to determine the file for which the QTSS_Read callback routine was called.

inFilePosition

The offset in bytes from the beginning of the file that represents the beginning of the section that is to be read. The server maintains the file position as an attribute of the file object, so the file system module does not have to cache the file position internally and can obtain the position at any time.

ioBuffer

A pointer to the buffer in which the file system module is to place the data that is read.

ioBufLen

The length of the buffer pointed to by ioBuffer.

outLenRead

The number of bytes actually read.

The file system module should make a best-effort attempt to fill the buffer pointed to by ioBuffer with data from the file that is being read starting with the position specified by inFilePosition.

If the file was opened with the qtssOpenFileAsync flag, the module should return QTSS_WouldBlock if reading the data will cause the thread to block. Otherwise, the module should block the thread until all of the data has become available. When the buffer pointed to by ioBuffer is full or the end of file has been reached, the file system module should set outLenRead to the number of bytes read and return QTSS_NoErr.

If the read fails for any reason, the file system module handling this role should return QTSS_RequestFailed.

File system modules do not need to explicitly register for this role.

Close File Role

The server calls modules for the Close File role in response to a module (or the server) calling the QTSS_CloseFile callback routine for a file object in order to close a file that has been opened.

When called, a Close File role receives a QTSS_CloseFile_Params structure, which is defined as follows:

typedef struct
{
    QTSS_Object         inFileObject;
} QTSS_CloseFile_Params;
inFileObject

The file object for the file that is to be closed. The file system module uses the file object to determine the file for which the QTSS_Close callback routine was called.

A module handling this role should dispose of any data structures that it has created for the file that is to be closed.

This role is always the last role for which a file system module will be invoked for any given file object.

File system modules do not need to explicitly register for this role.

Modules should always return QTSS_NoErr when they finish handling this role.

Request Event File Role

The server calls modules for the Request Event File role in response to a module (or the server) calling the QTSS_RequestEvent callback routine. If a module or the server calls the QTSS_OpenFileObject callback routine and specifies the qtssOpenFileAsync flag, the file system module handling that file object may return QTSS_WouldBlock from its Read File role. When that occurs, the caller of QTSS_Read may call QTSS_RequestEvent callback to tell the server that the caller of QTSS_Read wants to be notified when the data becomes available for reading.

When called, a Request Event File role receives a QTSS_RequestEventFile_Params structure, which is defined as follows:

typedef struct
{
    QTSS_Object     inFileObject;
    QTSS_EventType  inEventMask;
} QTSS_RequestEventFile_Params;
inFileObject

The file object for the file for which notifications are requested. The file system module uses the file object to determine the file for which the QTSS_RequestEvent callback routine was called.

inEventMask

A mask specifying the type of events for which notification is requested. Possible values are QTSS_ReadableEvent and QTSS_WriteableEvent.

If the file system that the file system module is implementing supports notification, the file system module should do whatever setup is necessary to receive an event for the file for which the QTSS_RequestEvent callback routine was called. When the file becomes readable, the file system module should call the QTSS_SignalStream callback routine and pass the stream reference for this file object (which can be obtained through the file object’s qtssFlObjStream attribute). Calling the QTSS_SignalStream callback routine tells the server that the caller of QTSS_RequestEvent should be notified that the file is now readable.

File system modules do not need to explicitly register for this role.

Modules should always return QTSS_NoErr when they finish handling this role.

Sample Code for the Open File Role

The sample code in Listing 3-8 handles the Open File role, but it could also be used to handle the Open File Preprocess role. This code uses the POSIX file system layer as the file system and does not support asynchronous I/O.

Listing 2-8  Handling the Open File Role

QTSS_Error OpenFile(QTSS_OpenFile_Params* inParams)
{
    // Use the POSIX open call to attempt to open the specified file.
    // If it doesn't exist, return QTSS_FileNotFound
 
    int theFile = open(inParams->inPath, O_RDONLY);
    if (theFile == -1)
        return QTSS_FileNotFound;
 
    // Use the POSIX stat call to get the length and the modification date
    // of the file. This information must be set in the QTSS_FileObject
    // by every file system module.
 
    UInt64 theLength = 0;
    time_t theModDate = 0;
    struct stat theStatStruct;
    if (::fstat(fFile, &theStatStruct) >= 0)
    {
        theLength = buf.st_size;
        theModDate = buf.st_mtime;
    }
    else
    {
        ::close(theFile);
        return QTSS_RequestFailed; // Stat failed
    }
 
    // Set the file length and the modification date attributes of this file
    // object before returning
 
    (void)QTSS_SetValue(inParams->inFileObject, qtssFlObjLength, 0, &theLength,  sizeof(theLength));
    (void)QTSS_SetValue(inParams->inFileObject, qtssFlObjModDate, 0, &theModDate,  sizeof(theModDate));
 
    // Place the file reference in a custom attribute in the QTSS_FileObject.
    // This way, we can easily get the file reference in other role handlers,
    // such as the QTSS_ReadFile_Role and the QTSS_CloseFile_Role.
 
    QTSS_Error theErr = QTSS_SetValue(inParams->inFileObject, sFileRefAttr, 0,
&theFile, sizeof(theFileSource));
 
    if (theErr != QTSS_NoErr)
    {
        ::close(theFile);
        return QTSS_RequestFailed;
    }
 
    return QTSS_NoErr;
}

Implementing Asynchronous Notifications

If a module, or the server, calls the QTSS_OpenFileObject and specifies the qtssOpenFileAsync flag, the file system module handling that file object may return QTSS_WouldBlock from its QTSS_ReadFile_Role handler. Once that happens, the caller of QTSS_Read may want to be notified when the requested data becomes available for reading. This is possible by calling the QTSS_RequestEvent callback, which tells the server that the caller would like to be notified when data is available to be read from the file.

Not all file systems support notification mechanisms, and if they do, the notification mechanisms are particular to each file system architecture. Therefore, whether a file system module supports notifications is at the discretion of the developer of the file system module. In general it is better for a file system module to support asynchronous notifications and not block in QTSS_ReadFile_Role because blocking on one file operation may disrupt service for many of the server’s clients.

Two facilities allow file system modules to implement notifications:

  • QTSS_RequestEventFile_Role, which is called in response to a module (or the server) calling the QTSS_RequestEvent callback on a file object. Modules do not need to explicitly register for this role. If a module doesn’t implement asynchronous notifications, it should return QTSS_RequestFailed from this role. If a module does implement asynchronous notifications, it should do whatever setup is necessary to receive an event for this file when the file becomes readable.

  • QTSS_SendEventToStream callback, called by a file system module when a file does become readable. Calling QTSS_SendEventToStream tells the server that the caller of QTSS_RequestEvent should be notified that the file is now readable.

Using the Admin Protocol

You can use the Admin protocol to communicate with QTSS. The Admin Protocol relies on the URI mechanism defined by RFC 2396 for specifying a container entity using a path and on the request and response mechanism for the Hypertext Transfer Protocol defined in RFC 1945.

The server’s internal data is mapped to a hierarchical tree of element arrays. Each element is a named type including a container type for retrieval of sub-node elements.

The server state machine and database can be accessed through a regular expression. The Admin Protocol abstracts the QTSS module API to handle data access and in some cases to provide data access triggers for execution of server functions.

Server streaming threads are blocked while the Admin Protocol accesses the server’s internal data. To minimize blocking, the Admin Protocol allows scoped access to the server’s data structures by allowing specific URL paths to any element.

The Admin Protocol uses the HTTP GET as the request and response method. At the end of each response, the session between client and server is closed. The Admin Protocol also supports the Authorization request header field as described in RFC 1945, section 10.2.

Access to Server Data

The Admin Protocol uses URIs to specify the location of server data. The following URI references the top level of the server’s hierarchical data tree using a simple HTTP GET request.

GET /modules/admin

Request Syntax

A valid request is an absolute reference followed by the server URI. An absolute reference is a path beginning with a forward slash character (/). A path represents the server’s virtual hierarchical data structure of containers and is expressed as a URL.

Here is the request syntax:

[absolute URL]?[parameters=”values"]+[command="value"]+["option"="value]

The following rules govern URIs:

  • /path is an absolute reference.

  • path/* is defined as all elements contained in the “path” container.

  • An asterisk (*) in the current URL location causes each element in that location to be iterated.

  • A question mark (?) indicates that options follow. Options are specified as name="value" pairs delimited by the plus (+) character.

  • Space and tab characters are treated as stop characters.

  • Values can be enclosed by the double quotation characters ("). Enclosing double quotation characters is required for values that contain spaces and tabs.

  • These characters cannot be used: period (.), two periods (..), and semicolon (;).

Here is an example of a request:

GET /modules/admin/server/qtssSvrClientSessions?parameters=rt+command=get

Request Functionality

Requests can contain an array iterator, a name lookup, a recursive tree walk, and a filtered response. All functions can execute in a single URI query.

Here is a request that gets the stream time scale and stream payload name for every stream in every session:

GET /modules/admin/server/qtssSvrClientSessions/*/qtssCliSesStreamObjects?
parameters=r+command=get+filter1=qtssRTPStrTimescale+filter2=qtssRTPStrPayloadName

where

  • * iterates the array of sessions

  • r in parameter=rt specifies a recursive walk and t specifies that data types are to be included in the result

  • filter=qtssRTPStrTimescale specifies that the stream time scale is to be returned

  • filter2=qtssRTPStrPayloadName specifies that the stream payload is to be returned

This request gets all server module names and their descriptions:

GET /modules/admin/server/qtssSvrModuleObjects?
parameters=r+command=get+filter2=qtssModDesc+filter1=qtssModName

The following example does a recursive search and gets all server attributes and their data types:

GET /modules/admin/server/?parameters=rt

The following examples return server attributes and their paths:

GET /modules/admin/server/*
GET /modules/admin/server/qtssSvrPreferences/*

Data References

All elements are arrays. Single element arrays may be referenced in any of the following ways:

  • path/element

  • path/element/

  • path/element/*

  • path/element/1

The references listed above are all evaluated as the same request.

Request Options

URIs that do not include a question mark (?) default to a GET request option.

URIs that include a question mark (?) must be followed by a "command=command-option" request option, where command-option is GET, SET, ADD, or DEL. URIs may also be followed by a "parameters=parameter-option" that refines the action of the command option.

Request options are not case-sensitive, but request option values are case-sensitive.

The Admin Protocol ignores any request option that it does not recognize as well any request options that a command does not require.

Command Options

The Admin Protocol recognizes the following command options:

Any unknown command option is reported as an error.

The effect of a command option may be modified by in the inclusion of one or more of the following modifiers:

  • value — used to specify a value

  • type — used to specify a data type

  • name — used to specify an element name

GET Command Option

The GET command option gets the data identified by the URI. It is the default command option. For that reason, it does not have to be specified, as shown in the following example:

GET /modules/admin/example_count

The GET command does not require any request options. If any request options were specified, they would be ignored.

SET Command Option

The SET command option sets the data identified by the URI. No value checking is performed. Conversion between the text value and the actual value is type-specific. Here are two examples of the SET command option:

GET /modules/admin/example_count?command=SET+value=5
GET /modules/admin/maxcount?command=SET+value=5+type=SInt32

If the type option is included in the command, type checking of the server element type and the set type is performed. If the types do not match, an error is returned and the command fails.

DEL Command Option

The DEL command option deletes the element referenced by the URL and any data it contains. Here is an example:

GET /modules/admin/maxcount?command=DEL

ADD Command Option

The ADD command option adds the data specified by the URI to the specified element.

If the end of the URL is an element, the ADD command performs an add to the array of elements referenced by the element name. The following example adds 6 to example_count if the data type of example_count is SInt16:

GET /modules/admin/example_count?command=ADD+value=6+type=SInt16

If the element at the end of the URL is a QTSS_Object container, the ADD command option adds the element to the container. The following example adds 5 to the element whose name is maxcount if the data type of maxcount is SInt16:

GET /modules/admin/?command=ADD+value=5+name=maxcount+type=SInt16

Parameter Options

Parameter options are single characters without delimiters that appear after the URL.

The Admin Protocol recognizes the following parameter options:

  • r — Walk downward in the hierarchy starting at end of the URL. Recursion should be avoided if “*” iterators or direct URL access to elements can be used instead.

  • v — Return the full path in name.

  • a — Return the access type.

  • t — Return the data type of value.

  • d — Return debugging information if an error occurs.

  • c — Return the count of elements in the path.

Here is an example that uses the r and t parameter options to recursively get the data type of all qtssSvrClientSessions:

GET /modules/admin/server/qtssSvrClientSessions?parameters=rt+command=get

Attribute Access Types

The following access types are used to control access to server data:

  • r — Read access type

  • w — Write access type

  • p — Preemptive safe access type

Data Types

Data types can be any server-allowed text value. New data types can be defined and returned by the server, so data types are not limited to the basic set listed here:

UInt8

SInt16

UInt64

Float64

char

SInt8

UInt32

SInt64

Bool8

QTSS_Object

UInt16

SInt32

Float32

Bool16

void_pointer

Values of type QTSS_Object, pointers, and unknown data types always converted to a host-ordered string of hexadecimal values. Here is an example of a hexadecimal value result:

unknown_pointer=halogen; type=void_pointer

Server Responses

This section describes the data that is returned in response to a request. The information on response data is organized in the following sections:

Unauthorized Response

Here is an example of an unauthorized response:

HTTP/1.1 401 Unauthorized
WWW-Authenticate: Basic realm="QTSS/modules/admin"
Server: QTSS
Connection: Close
Content-Type: text/plain

OK Response

Here is an example of an “OK” response:

HTTP/1.0 200 OK
Server: QTSS/4.0 [v408]-MacOSX
Connection: Close
Content-Type: text/plain
Container="/"
admin/
error:(0)

All OK responses end with error:(0).

Response Data

All entity references in response data follow this form:

[NAME=VALUE];[attribute="value"],[attribute=value"]

where brackets ([ ]) indicate that the enclosed response data is optional. Therefore, the response data may take the following forms:

NAME=VALUE

NAME=VALUE;attribute="value"

NAME=VALUE;attribute="value",attribute="value"

All container references follow this form:

[NAME/];[attribute="value"],[attribute="value"]

where brackets ([ ]) indicate that the enclosed response data is optional. Therefore, response data may take the following forms:

NAME/

NAME/;attribute="value"

NAME;attribute="value",attribute="value"

The order of appearance of container references and the container’s entity references are important. This is especially true when the response is a recursive walk of a container hierarchy.

Each new level in the hierarchy must begin with a Container= reference. Each container list of elements must be a complete list of the contained elements and any containers. The appearance of a Container= reference indicates the end of a previous container’s contents and the beginning of a new container.

This example shows how each new container is identified with a unique path:

Container="/level1/"
field1="value"
field2="value"
level2a/
level2b/
Container="/level1/level2a/"
field1="value"
level3a/
level3b/
Container="/level1/level2a/level3a"
field1="value"
Container="/level1/level2a/level3b"
Container="/level1/level2b/"
field1="value"
level3a/
Container="/level1/level2b/level3a/"
field1="value"

Array Values

For arrays of elements, a numerical value represents the index. Arrays are containers. Here is an example:

Container="/level1/"
field1="value"
field2="value"
array1/
Container="/level1/array1/"
1=value
2=value

Array elements may be containers, as shown in this example:

Container="/level1/array1/"
1/
2/
3/
 
Container="/level1/array1/1/"
field1="value"
field2="value"
Container="/level1/array1/2/"
Container="/level1/array1/3/"
field1="value"

Response Root

The root for responses is /admin.

Errors in Responses

For each response, the error state for the request is reported at the end of the data. Here are some examples:

Error:(0) indicates that no error occurred

Error:(404) indicates that no data was found

The number enclosed by parentheses is an HTTP error code followed by an error string when debugging is turned on using the "parameters=d" query option. Here is an example:

error:(404);reason="No data found"

Request and Response Examples

An easy way to make requests is to use a web browser and a URL like this:

http://IP-address:554/modules/admin/?parameters=a+command=get

The following example uses basic authentication and shows the HTTP response headers:

Request: GET /modules/admin?parameters=a+command=get

Authorization: Basic QWXtaW5pT3RXYXRvcjXkZWZhdWx0

Response:

HTTP/1.0 200 OK
Server: QTSS/4.0 [v408]-MacOSX
Connection: Close
Content-Type: text/plain
 
Container="/"
admin/;a=r
error:(0)

The following recursive request gets the value of each element in /modules/admin:

GET /modules/admin?command=get+parameters=r

The following recursive request returns the access type and data type for the value of each element in /modules/admin:

GET /modules/admin?command=get+parameters=rat

The following request gets the elements in /modules/admin. Note that the GET command option is not required because request options are not present.

GET /modules/admin/*

A request like the following can be used to monitor the session list:

GET /modules/admin/server/qtssSvrClientSessions/*

The response is a list of unique qtssSvrClientSessions session IDs. Here is an example::

Container="/admin/server/qtssSvrClientSessions/"
12/
2/
4/
8/
error:(0)

The following request gets the indexes for the qtssCliSesStreamObjects object, which is an indexed array of streams:

GET /modules/admin/server/qtssSvrClientSessions/*/qtssCliSesStreamObjects/*

The response might look like this:

Container="/admin/server/qtssSvrClientSessions/3/qtssCliSesStreamObjects/"
0/
1/
error:(0)

Here is another request:

GET /modules/admin/server/qtssSvrClientSessions/3/qtssCliSesStreamObjects/0/*

And here is a typical response:

qtssRTPStrTrackID="4"
qtssRTPStrSSRC="683618521"
qtssRTPStrPayloadName="X-QT/600"
qtssRTPStrPayloadType="1"
qtssRTPStrFirstSeqNumber="-7111"
qtssRTPStrFirstTimestamp="433634204"
qtssRTPStrTimescale="600"
qtssRTPStrQualityLevel="0"
qtssRTPStrNumQualityLevels="3"
qtssRTPStrBufferDelayInSecs="3.000000"
qtssRTPStrFractionLostPackets="0"
qtssRTPStrTotalLostPackets="52"
qtssRTPStrJitter="0"
qtssRTPStrRecvBitRate="1526072"
qtssRTPStrAvgLateMilliseconds="501"
qtssRTPStrPercentPacketsLost="0"
qtssRTPStrAvgBufDelayInMsec="30"
qtssRTPStrGettingBetter="0"
qtssRTPStrGettingWorse="0"
qtssRTPStrNumEyes="0"
qtssRTPStrNumEyesActive="0"
qtssRTPStrNumEyesPaused="0"
qtssRTPStrTotPacketsRecv="6763"
qtssRTPStrTotPacketsDropped="0"
qtssRTPStrTotPacketsLost="0"
qtssRTPStrClientBufFill="0"
qtssRTPStrFrameRate="0"
qtssRTPStrExpFrameRate="3903"
qtssRTPStrAudioDryCount="0"
qtssRTPStrIsTCP="false"
qtssRTPStrStreamRef="18861508"
qtssRTPStrCurrentPacketDelay="-2"
qtssRTPStrTransportType="0"
qtssRTPStrStalePacketsDropped="0"
qtssRTPStrTimeFlowControlLifted="974373815109"
qtssRTPStrCurrentAckTimeout="0"
qtssRTPStrCurPacketsLostInRTCPInterval="52"
qtssRTPStrPacketCountInRTCPInterval="689"
QTSSReflectorModuleStreamCookie=(null)
qtssNextSeqNum=(null)
qtssSeqNumOffset=(null)
QTSSSplitterModuleStreamCookie=(null)
QTSSFlowControlModuleLossAboveTol="0"
QTSSFlowControlModuleLossBelowTol="3"
QTSSFlowControlModuleGettingWorses="0"
error:(0)

Here is an request that returns the IP addresses of connected clients:

GET /modules/admin/server/qtssSvrClientSessions/*/qtssCliRTSPSessRemoteAddrStr

And here is a typical response:

Container="/admin/server/qtssSvrClientSessions/5/ "qtssCliRTSPSessRemoteAddrStr=17.221.40.1
Container="/admin/server/qtssSvrClientSessions/6/ "qtssCliRTSPSessRemoteAddrStr=17.221.40.2
Container="/admin/server/qtssSvrClientSessions/8/ "qtssCliRTSPSessRemoteAddrStr=17.221.40.3
Container="/admin/server/qtssSvrClientSessions/14/ "qtssCliRTSPSessRemoteAddrStr=17.221.40.4
error:(0)

Changing Server Settings

To change a server setting, the entity name and the value to be set are specified in the request body. If a match is made on the URL base and entity name at the current container level and if the setting is writable, the value is set.

base = /base/container
name = value
/base/container/name="value"

Getting and Setting Preferences

Preferences paths are useful for getting and setting a server or module preference. Setting a preference causes the preference’s new value to be flushed to the server’s XML preference file. The new value takes effect immediately.

Server preferences are stored in /modules/admin/server/qtssSvrPreferences. Module preferences are stored in /modules/admin/server/qtssSvrModuleObjects/*/qtssModPrefs/.

The elements defined in the qtssSvrPreferences object can only be modified — they cannot be deleted.

The elements defined in qtssModPrefs can be added to, deleted, and modified.

A module or the server can automatically restore some deleted elements if the elements are needed by a module or the server. When applied to a qtssModPrefs element, the ADD, DEL, and SET commands cause the streaming server’s XML preference file to be rewritten.

Getting and Changing the Server’s State

The qtssSvrState attribute controls the server’s state. The path is /modules/admin/server/qtssSvrState. It can be modified as a UInt32 with the following values.

qtssStartingUpState     = 0,
qtssRunningState        = 1,
qtssRefusingConnectionsState  = 2,
qtssFatalErrorState     = 3,
qtssShuttingDownState   = 4,
qtssIdleState           = 5