virtual void OnNackNotification(BLT_DecoderServer_Message::CommandId id, BLT_Result result_code) {
        ATX_LOG_FINE_2("NACK: %d, result=%d", id, result_code);
        if (!m_JniEnv || !m_Delegate || !m_DelegateMethod) return;

        jintArray array = (jintArray)m_JniEnv->NewIntArray(2);
        jint values[2] = {MapCommandId(id), (jint)result_code};
        m_JniEnv->SetIntArrayRegion(array, 0, 2, values);
        
        m_JniEnv->CallVoidMethod(m_Delegate, m_DelegateMethod, com_bluetune_player_Player_MESSAGE_TYPE_NACK, NULL, array);
    }
Example #2
0
/*----------------------------------------------------------------------
|    CrossFader_OnPropertyChanged
+---------------------------------------------------------------------*/
BLT_VOID_METHOD
CrossFader_OnPropertyChanged(ATX_PropertyListenerInstance* instance,
                             ATX_CString                   name,
                             ATX_PropertyType              type,
                             const ATX_PropertyValue*      value)
{
    /*CrossFader* fader = (CrossFader*)instance;*/
    BLT_COMPILER_UNUSED(instance);
    BLT_COMPILER_UNUSED(type);
    ATX_LOG_FINE_2("CrossFader::OnPropertyChanged - name=%s val=%d", 
                   name ? name : "*", value ? value->integer : 0);
}
Example #3
0
/*----------------------------------------------------------------------
|   BLT_TcpNetworkStream_Create
+---------------------------------------------------------------------*/
BLT_Result 
BLT_TcpNetworkStream_Create(const char* name, ATX_InputStream** stream)
{
    ATX_Socket* sock;
    ATX_String  hostname = ATX_String_Create(name);
    ATX_UInt16  port = BLT_TCP_NETWORK_STREAM_DEFAULT_PORT;
    int         sep;
    ATX_Result  result = ATX_SUCCESS;

    /* default */
    *stream = NULL;

    /* parse the hostname/port */
    sep = ATX_String_FindCharFrom(&hostname, ':', 6);
    if (sep > 0) {
        /* we have a port number */
        int port_long = 0;
        result = ATX_ParseInteger(name+sep+1, &port_long, ATX_FALSE);
        if (ATX_FAILED(result)) {
            ATX_LOG_WARNING("BLT_TcpNetworkStream_Create - invalid port spec");
            goto end;
        }
        port = (ATX_UInt16)port_long;
        ATX_String_SetLength(&hostname, sep);
    }

    /* create a socket */
    result = ATX_TcpClientSocket_Create(&sock);
    if (ATX_FAILED(result)) goto end;
    
    /* connect */
    ATX_LOG_FINE_2("BLT_TcpNetworkStream_Create - connecting to %s:%d",
                   ATX_CSTR(hostname), port);
    result = ATX_Socket_ConnectToHost(sock, ATX_CSTR(hostname), port, BLT_TCP_NETWORK_STREAM_DEFAULT_TIMEOUT);
    if (ATX_FAILED(result)) {
        ATX_LOG_WARNING_1("BLT_TcpNetworkStream_Create - failed to connect (%d)", result);
        goto end;
    }
    ATX_LOG_FINE("BLT_TcpNetworkStream_Create - connected");

    /* return the input stream */
    result = ATX_Socket_GetInputStream(sock, stream);

    /* release the socket */
    ATX_DESTROY_OBJECT(sock);
    
end:
    ATX_String_Destruct(&hostname);
    return result;
}
    virtual void OnStreamPositionNotification(BLT_StreamPosition& position) {
        ATX_LOG_FINE_2("STREAM-POSITION: %d/%d", (int)position.offset, (int)position.range);
        if (!m_JniEnv || !m_Delegate || !m_DelegateMethod) return;

        double fpos = 0.0;
        if (position.range) {
            fpos = (double)position.offset/(double)position.range;
        }
        jintArray array = (jintArray)m_JniEnv->NewIntArray(2);
        jint values[2] = {(jint)(fpos*10000.0), (jint)10000};
        m_JniEnv->SetIntArrayRegion(array, 0, 2, values);

        m_JniEnv->CallVoidMethod(m_Delegate, m_DelegateMethod, com_bluetune_player_Player_MESSAGE_TYPE_STREAM_POSITION, NULL, array);
    }
/*----------------------------------------------------------------------
|    BLT_DecoderServer::OnSeekToPositionCommnand
+---------------------------------------------------------------------*/
void
BLT_DecoderServer::OnSeekToPositionCommand(BLT_UInt64 offset, BLT_UInt64 range)
{
    BLT_Result result;
    ATX_LOG_FINE_2("[%d:%d]", (int)offset, (int)range);
    result = BLT_Decoder_SeekToPosition(m_Decoder, offset, range);
    if (BLT_SUCCEEDED(result)) {
        UpdateStatus();

        // update the state we were in the STATE_EOS state
        if (m_State == STATE_EOS) SetState(STATE_STOPPED);
    }

    SendReply(BLT_DecoderServer_Message::COMMAND_ID_SEEK_TO_POSITION, result);
}
/*----------------------------------------------------------------------
|    BLT_DecoderServer::OnSetInputCommand
+---------------------------------------------------------------------*/
void
BLT_DecoderServer::OnSetInputCommand(BLT_CString name, BLT_CString type)
{
    BLT_Result result;

    ATX_LOG_FINE_2("set input (%s / %s)",
                   BLT_SAFE_STRING(name), BLT_SAFE_STRING(type));
    result = BLT_Decoder_SetInput(m_Decoder, name, type);

    // update the state if we were in the STATE_EOS state
    if (m_State == STATE_EOS) SetState(STATE_STOPPED);
    
    UpdateStatus();
    SendReply(BLT_DecoderServer_Message::COMMAND_ID_SET_INPUT, result);
}
/*----------------------------------------------------------------------
|    BLT_DecoderServer::SetState
+---------------------------------------------------------------------*/
BLT_Result
BLT_DecoderServer::SetState(State state)
{
    // shortcut
    if (state == m_State) return BLT_SUCCESS;

    ATX_LOG_FINE_2("state change from %d to %d", m_State, state);

    m_State = state;

    // notify the client
    m_Client->PostMessage(
        new BLT_DecoderClient_DecoderStateNotificationMessage(state));

    return BLT_SUCCESS;
}
/*----------------------------------------------------------------------
|    BLT_DecoderServer::OnSetOutputCommand
+---------------------------------------------------------------------*/
void
BLT_DecoderServer::OnSetOutputCommand(BLT_CString name, BLT_CString type)
{
    BLT_Result result;

    ATX_LOG_FINE_2("set output (%s / %s",
                   BLT_SAFE_STRING(name), BLT_SAFE_STRING(type));
    result = BLT_Decoder_SetOutput(m_Decoder, name, type);
    if (BLT_SUCCEEDED(result)) {
        // notify of the new volume
        float volume=0.0f;
        result = BLT_Decoder_GetVolume(m_Decoder, &volume);
        if (BLT_SUCCEEDED(result)) {
            m_Client->PostMessage(new BLT_DecoderClient_VolumeNotificationMessage(volume));
        }
        result = BLT_SUCCESS;
    }
    SendReply(BLT_DecoderServer_Message::COMMAND_ID_SET_OUTPUT, result);
}
Example #9
0
/*----------------------------------------------------------------------
|    ATX_HttpMessage_Emit
+---------------------------------------------------------------------*/
static ATX_Result
ATX_HttpMessage_Emit(const ATX_HttpMessage* message, ATX_OutputStream* stream)
{
    ATX_ListItem* item = ATX_List_GetFirstItem(message->headers);

    /* output the headers */
    while (item) {
        ATX_HttpHeader* header = ATX_ListItem_GetData(item);
        if (header && 
            !ATX_String_IsEmpty(&header->name) && 
            !ATX_String_IsEmpty(&header->value)) {
            ATX_OutputStream_WriteString(stream, ATX_CSTR(header->name));
            ATX_OutputStream_Write(stream, ": ", 2, NULL);
            ATX_OutputStream_WriteLine(stream, ATX_CSTR(header->value));
            ATX_LOG_FINE_2("ATX_HttpMessage::Emit - %s: %s", ATX_CSTR(header->name), ATX_CSTR(header->value));
        }
        item = ATX_ListItem_GetNext(item);
    }

    return ATX_SUCCESS;
}
Example #10
0
/*----------------------------------------------------------------------
|    ATX_HttpResponse_Parse
+---------------------------------------------------------------------*/
static ATX_Result 
ATX_HttpResponse_Parse(ATX_HttpResponse* response, ATX_InputStream* stream)
{
    char        buffer[ATX_HTTP_MAX_LINE_SIZE+1];
    char*       line = buffer;
    char*       find;
    ATX_Boolean header_pending = ATX_FALSE;
    ATX_String  header_name = ATX_EMPTY_STRING;
    ATX_String  header_value = ATX_EMPTY_STRING;
    ATX_Result  result;

    /* get the first line from the stream */
    result = ATX_InputStream_ReadLine(stream, line, sizeof(buffer), NULL);
    if (ATX_FAILED(result)) return result;

    /* get the protocol */
    find = (char*)ATX_Http_FindChar(line, ' ');
    if (find == NULL) {
        return ATX_ERROR_INVALID_SYNTAX;
    }
    *find = '\0';
    ATX_String_Assign(&response->base.protocol, line);

    /* get the status code */
    line = (char*)ATX_Http_SkipWhitespace(find+1);
    find = (char*)ATX_Http_FindChar(line, ' ');
    if (find == NULL) {
        return ATX_ERROR_INVALID_SYNTAX;
    }
    *find = '\0';
    if (ATX_StringLength(line) != 3) {
        return ATX_ERROR_INVALID_SYNTAX;
    }
    {
        int i;
        response->status_code = 0;
        for (i=0; i<3; i++) {
            if (line[i] < '0' || line[i] > '9') {
                return ATX_ERROR_INVALID_SYNTAX;
            }
            response->status_code *= 10;
            response->status_code += line[i]-'0';
        }
    }

    /* the rest is the reason phrase */
    line = (char*)ATX_Http_SkipWhitespace(find+1);
    ATX_String_Assign(&response->reason_phrase, line);

    /* parse headers until an empty line or end of stream */
    do {
        /* read a line */
        result = ATX_InputStream_ReadLine(stream, line, sizeof(buffer), NULL);
        if (ATX_FAILED(result)) break;

        /* stop if line is empty */
        if (line[0] == '\0' || line[0] == '\r' || line[0] == '\n') {
            if (header_pending) {
                ATX_String_TrimWhitespace(&header_value);
                ATX_HttpMessage_SetHeader((ATX_HttpMessage*)response, 
                                          ATX_CSTR(header_name), 
                                          ATX_CSTR(header_value));
                ATX_LOG_FINE_2("ATX_HttpResponse::Parse - %s: %s",
                               ATX_CSTR(header_name),
                               ATX_CSTR(header_value));
            }
            break;
        }

        /* process the line */
        if ((line[0] == ' ' || line[0] == '\t') && header_pending) {
            /* this is a line continuation */
            ATX_String_Append(&header_value, line+1);
        } else {
            /* this is a new header */
            const char* name;
            const char* value;

            /* add the pending header to the list */
            if (header_pending) {
                ATX_String_TrimWhitespace(&header_value);
                ATX_HttpMessage_SetHeader((ATX_HttpMessage*)response, 
                                          ATX_CSTR(header_name), 
                                          ATX_CSTR(header_value));
                ATX_LOG_FINE_2("ATX_HttpResponse::Parse - %s: %s",
                               ATX_CSTR(header_name),
                               ATX_CSTR(header_value));
            }

            /* parse header name */
            name = ATX_Http_SkipWhitespace(line);
            value = ATX_Http_FindChar(name, ':');
            ATX_String_AssignN(&header_name, name, (ATX_Size)(value-name));
            value = ATX_Http_SkipWhitespace(value+1);
            ATX_String_Assign(&header_value, value);

            /* don't add the header now, it could be continued */
            header_pending = ATX_TRUE;
        }
    } while(ATX_SUCCEEDED(result));

    /* keep a reference to the stream */
    response->base.body = stream;
    ATX_REFERENCE_OBJECT(stream);

    /* cleanup */
    ATX_String_Destruct(&header_name);
    ATX_String_Destruct(&header_value);

    return ATX_SUCCESS;
}
/*----------------------------------------------------------------------
|   DcfParser_ParseV2Header
+---------------------------------------------------------------------*/
static BLT_Result
DcfParser_ParseV2Header(DcfParser* self, ATX_InputStream* stream)
{
    /* rewind the byte stream */
    ATX_InputStream_Seek(stream, 0);

    /* parse the atoms from the stream */
    AP4_ByteStream* mp4_stream = new ATX_InputStream_To_AP4_ByteStream_Adapter(stream);
    AP4_AtomParent  atoms;
    AP4_Result result = AP4_DefaultAtomFactory::Instance.CreateAtomsFromStream(*mp4_stream, atoms);
    mp4_stream->Release();
    
    AP4_ByteStream* decrypting_stream = NULL;
    AP4_ContainerAtom* odrm = dynamic_cast<AP4_ContainerAtom*>(atoms.GetChild(AP4_ATOM_TYPE_ODRM));
    if (odrm) {
        AP4_OdheAtom* odhe = dynamic_cast<AP4_OdheAtom*>(odrm->GetChild(AP4_ATOM_TYPE_ODHE));
        AP4_OddaAtom* odda = dynamic_cast<AP4_OddaAtom*>(odrm->GetChild(AP4_ATOM_TYPE_ODDA));
        if (odhe && odda) {
            const char* content_id = "";

            /* get the content ID */
            AP4_OhdrAtom* ohdr = dynamic_cast<AP4_OhdrAtom*>(odhe->GetChild(AP4_ATOM_TYPE_OHDR));
            if (ohdr) {
                content_id = ohdr->GetContentId().GetChars();
            }

            /* get the content key */
            NPT_DataBuffer key;
            result = DcfParser_GetContentKey(self, content_id, key);
            if (BLT_FAILED(result)) {
                ATX_LOG_FINE_2("GetKeyForContent(%s) returned %d", 
                               content_id, 
                               result);
                return BLT_ERROR_NO_MEDIA_KEY;
            }

            /* create the decrypting stream */
            result = AP4_OmaDcfAtomDecrypter::CreateDecryptingStream(*odrm,
                                                                     key.GetData(),
                                                                     key.GetDataSize(),
                                                                     self->cipher_factory,
                                                                     decrypting_stream);
            if (AP4_SUCCEEDED(result)) {
                /* update the content type */
                ATX_CopyStringN(self->input.content_type,
                                odhe->GetContentType().GetChars(),
                                sizeof(self->input.content_type));
                    
                /* update the encrypted size */
                self->input.encrypted_size = odda->GetEncryptedDataLength();
            }
        }
    }

    /* check that we have found what we needed in the atoms */
    if (decrypting_stream == NULL) return BLT_ERROR_INVALID_MEDIA_FORMAT;

    /* update the output size */
    AP4_LargeSize plaintext_size = 0;
    if (AP4_SUCCEEDED(decrypting_stream->GetSize(plaintext_size))) {
        self->output.size = plaintext_size;
    } else {
        self->output.size = self->input.encrypted_size;
    }
    
    /* create a reverse adapter */
    result = AP4_ByteStream_To_ATX_InputStream_Adapter_Create(decrypting_stream, &self->output.stream);
    decrypting_stream->Release();

    return BLT_SUCCESS;
}
/*----------------------------------------------------------------------
|   DcfParser_ParseV1Header
+---------------------------------------------------------------------*/
static BLT_Result
DcfParser_ParseV1Header(DcfParser* self, ATX_InputStream* stream)
{
    /* rewind the byte stream */
    ATX_InputStream_Seek(stream, 0);

    /* user a buffer for parsing */
    ATX_UInt8 buffer[3];
    
    /* read the first 3 fields */
    ATX_Result result;
    result = ATX_InputStream_ReadFully(stream, buffer, 3);
    if (ATX_FAILED(result)) return result;

    /* check the version */
    ATX_UInt8 version = buffer[0];
    if (version != 1) {
        ATX_LOG_FINE_1("unsupported DCF version (%d)", version);
        return BLT_ERROR_UNSUPPORTED_FORMAT;
    }
    
    /* read the content type */
    ATX_UInt8 content_type_length = buffer[1];
    self->input.content_type[content_type_length] = 0; // null-terminate
    result = ATX_InputStream_ReadFully(stream, self->input.content_type, content_type_length);
    if (ATX_FAILED(result)) return result;
    
    /* read the content URI */
    ATX_UInt8 content_uri_length = buffer[2];
    self->input.content_uri[content_uri_length] = 0; // null-terminate
    result = ATX_InputStream_ReadFully(stream, self->input.content_uri, content_uri_length);
    if (ATX_FAILED(result)) return result;
    
    /* read the variable-length fields */
    unsigned int var_length_1, var_length_2;
    ATX_UInt32 headers_length = 0;
    result = DcfParser_ReadUintvar(stream, headers_length, var_length_1);
    if (ATX_FAILED(result)) return result;
    ATX_UInt32 data_length = 0;
    result = DcfParser_ReadUintvar(stream, data_length, var_length_2);
    if (ATX_FAILED(result)) return result;

    /* check that the encrypted size makes sense */
    if (data_length < 32) return BLT_ERROR_INVALID_MEDIA_FORMAT;
    self->input.encrypted_size = data_length;
    
    /* get the content key */
    NPT_DataBuffer key;
    result = DcfParser_GetContentKey(self, self->input.content_uri, key);
    if (BLT_FAILED(result)) {
        ATX_LOG_FINE_2("GetKeyForContent(%s) returned %d", 
            self->input.content_uri, 
            result);
        return BLT_ERROR_NO_MEDIA_KEY;
    }

    /* read the headers */
    if (headers_length > BLT_DCF_PARSER_MAX_HEADERS_LENGTH) return BLT_ERROR_INVALID_MEDIA_FORMAT;
    char* headers = new char[headers_length+1];
    headers[headers_length] = '\0';
    result = ATX_InputStream_ReadFully(stream, headers, headers_length);
    if (ATX_FAILED(result)) {
        delete[] headers;
        return result;
    }
    
    /* as a first-order estimate, set the output size to the encrypted size minus the IV */
    self->output.size = self->input.encrypted_size-16;
        
    /* parse the headers */
    NPT_MemoryStream* headers_memory_stream = new NPT_MemoryStream(headers, headers_length);
    NPT_InputStreamReference headers_memory_stream_ref(headers_memory_stream);
    NPT_BufferedInputStream* headers_buffered_stream = 
        new NPT_BufferedInputStream(headers_memory_stream_ref);
    NPT_HttpHeaders content_headers;
    content_headers.Parse(*headers_buffered_stream);
    delete headers_buffered_stream;
    delete[] headers;
    
    /* find out about the encryption from the headers */
    const NPT_String* encryption_method = content_headers.GetHeaderValue("Encryption-Method");
    if (encryption_method == NULL) return BLT_ERROR_INVALID_MEDIA_FORMAT;
    
    /* check the encryption method */
    bool                           encryption_supported = false;
    NPT_Map<NPT_String,NPT_String> encryption_params;
    NPT_Size                       algorithm_id_length = 0;
    
    /* parse the algorithm id */
    int separator = encryption_method->Find(';', 0, true);
    if (separator > 0) {
        algorithm_id_length = separator;
        
        /* parse the params */
        result = NPT_ParseMimeParameters(((const char*)(*encryption_method))+separator+1, 
                                         encryption_params);
        if (NPT_FAILED(result)) {
            ATX_LOG_FINE_1("cannot parse Encryption-Method parameters (%s)", 
                           (const char*)encryption_method);
            return BLT_ERROR_INVALID_MEDIA_FORMAT;
        }

        /* parse the plaintext-length header, if present */
        NPT_String* plaintext_length = NULL;
        if (NPT_SUCCEEDED(encryption_params.Get("plaintext-length", plaintext_length))) {
            NPT_UInt64 value = 0;
            if (NPT_SUCCEEDED(plaintext_length->ToInteger64(value, true))) {
                self->output.size = value;
            }
        }
    } else {
        algorithm_id_length = encryption_method->GetLength();
    }       

    if (NPT_StringsEqualN((const char*)(*encryption_method),
                          BLT_DCF_PARSER_ALGORITHM_ID_AES128CBC,
                          algorithm_id_length)) {
        encryption_supported = true;
        NPT_String* padding = NULL;
        if (NPT_SUCCEEDED(encryption_params.Get("padding", padding))) {
            if (*padding == BLT_DCF_PARSER_PADDING_RFC2630) {
                encryption_supported = false; // hmmm, unknown padding
            }
        } 
    }

    if (!encryption_supported) {
        ATX_LOG_FINE_1("unsupported encryption format (%s)", encryption_method);
        return BLT_ERROR_UNSUPPORTED_FORMAT;
    }
    
    /* read the IV */
    AP4_UI08 iv[16];
    result = ATX_InputStream_ReadFully(stream, iv, sizeof(iv));
    if (BLT_FAILED(result)) return result;
    
    /* create a byte stream to represent the encrypted data */
    ATX_Size header_size = 3+content_type_length+content_uri_length+var_length_1+var_length_2+headers_length;
    ATX_InputStream* data_stream = NULL;
    ATX_SubInputStream_Create(stream, 
                              header_size+16, // skip the IV
                              self->input.encrypted_size-16, 
                              NULL, 
                              &data_stream);
    ATX_InputStream_To_AP4_ByteStream_Adapter* encrypted_stream = 
        new ATX_InputStream_To_AP4_ByteStream_Adapter(data_stream);
    ATX_RELEASE_OBJECT(data_stream);
    
    /* create a decrypting stream for the content */ // FIXME: temporary
    AP4_ByteStream* decrypting_stream = NULL;
    result = AP4_DecryptingStream::Create(AP4_BlockCipher::CBC,
                                          *encrypted_stream,
                                          self->output.size,
                                          iv, 16, 
                                          key.GetData(),
                                          key.GetDataSize(),
                                          self->cipher_factory,
                                          decrypting_stream);
    encrypted_stream->Release();
    if (AP4_FAILED(result)) return result;

    /* create a reverse adapter */
    result = AP4_ByteStream_To_ATX_InputStream_Adapter_Create(decrypting_stream, &self->output.stream);
    decrypting_stream->Release();

    return BLT_SUCCESS;
}
/*----------------------------------------------------------------------
|   OsxAudioUnitsOutput_MapDeviceIndex
+---------------------------------------------------------------------*/
static BLT_Result
OsxAudioUnitsOutput_MapDeviceName(const char* name, AudioDeviceID* device_id)
{
    OSStatus       err;
    BLT_Result     result = BLT_ERROR_NO_SUCH_DEVICE;
    UInt32         prop_size = 0;
    AudioDeviceID* devices = NULL;
    unsigned int   device_count = 0;
    ATX_UInt32     device_index = 0;
    unsigned int   device_selector = 1;
    char*          device_name = NULL;
    unsigned int   i;
    
    /* setup a default value */
    *device_id = 0;
    
    /* check the parameters */
    if (name[0] == '\0') return BLT_ERROR_NO_SUCH_DEVICE;
    
    /* parse the name */
    if (name[0] == '#') {
        ++name;
        ATX_LOG_FINE_1("device name = %s", name);
    } else {
        if (BLT_FAILED(ATX_ParseInteger32U(name, &device_index, ATX_FALSE))) {
            return BLT_ERROR_NO_SUCH_DEVICE;
        }
        ATX_LOG_FINE_1("device index = %d", device_index);
        name = NULL;
        
        /* 0 means default */
        if (device_index == 0) {
            /* look at the device's streams */
            AudioDeviceID default_device_id = 0;
            prop_size = sizeof(AudioDeviceID);
            err = AudioHardwareGetProperty(kAudioHardwarePropertyDefaultOutputDevice, &prop_size, &default_device_id);
            if (err == noErr) {
                prop_size = 0;
                err = AudioDeviceGetPropertyInfo(default_device_id, 0, FALSE, kAudioDevicePropertyStreams, &prop_size, NULL);
                if (err == noErr) {
                    ATX_LOG_FINE_1("device has %d streams", (int)prop_size/4);
                }
            }
            return BLT_SUCCESS;
        }
    }
    
    /* ask how many devices exist */
    err = AudioHardwareGetPropertyInfo(kAudioHardwarePropertyDevices, &prop_size, NULL);
    if (err != noErr) {
        ATX_LOG_FINE_1("AudioHardwareGetPropertyInfo failed (%d)", (int)err);
        return 0;
    }
    device_count = prop_size/sizeof(AudioDeviceID);
    ATX_LOG_FINE_1("found %d devices", device_count);
    
    /* allocate memory for the array */
    devices = (AudioDeviceID*)ATX_AllocateZeroMemory(sizeof(AudioDeviceID) * device_count);
    if (devices == NULL) return 0;
    
    /* retrieve the list of device IDs */
    err = AudioHardwareGetProperty(kAudioHardwarePropertyDevices, &prop_size, devices);
    if (err != noErr) {
        ATX_LOG_FINE_1("AudioHardwareGetProperty(kAudioHardwarePropertyDevices) failed (%d)", (int)err);
        return 0;
    }
    
    /* find the device ID we want */
    for (i=0; i<device_count; i++) {
        /* get the device name */
        err = AudioDeviceGetPropertyInfo(devices[i], 0, false,
                                         kAudioDevicePropertyDeviceName,
                                         &prop_size, NULL);
        if (err == noErr) {
            if (device_name) ATX_FreeMemory(device_name);
            device_name = (char*)ATX_AllocateZeroMemory(prop_size+1);
            err = AudioDeviceGetProperty(devices[i], 0, false,
                                         kAudioDevicePropertyDeviceName,
                                         &prop_size, device_name);
            if (err == noErr) {
                ATX_LOG_FINE_2("device name [%d] = %s", i, device_name);
            }
            /* cleanup the string */
            device_name[prop_size] = '\0'; /* NULL-terminate the string */
            if (prop_size > 0) {
                unsigned int x;
                for (x=prop_size-1; x; x--) {
                    if (device_name[x] == ' ') {
                        device_name[x] = '\0';
                    } else if (device_name[x]) {
                        break;
                    }
                }
            }
        } else {
            continue;
        }
        
        /* look at the device's streams */
        prop_size = 0;
        err = AudioDeviceGetPropertyInfo(devices[i], 0, FALSE, kAudioDevicePropertyStreams, &prop_size, NULL);
        if (err != noErr || prop_size == 0) {
            ATX_LOG_FINE("skipping device (not an output)");
            continue;
        }

        if (name) {
            /* look for a match by name */
            if (ATX_StringsEqual(device_name, name)) {
                *device_id = devices[i];
                ATX_LOG_FINE("device selected as output");
                result = BLT_SUCCESS;
                break;
            }
        } else {
            /* look for a match by index */
            if (device_selector++ == device_index) {
                *device_id = devices[i];
                ATX_LOG_FINE("device selected as output");
                result = BLT_SUCCESS;
                break;
            }
        }
    }
    
    if (device_name) ATX_FreeMemory(device_name);
    if (devices)     ATX_FreeMemory(devices);
    
    return result;
}