예제 #1
0
/*----------------------------------------------------------------------
|   Mp4Parser_SetupAudioOutput
+---------------------------------------------------------------------*/
static BLT_Result
Mp4Parser_SetupAudioOutput(Mp4Parser* self, AP4_Movie* movie)
{
    ATX_Properties* properties = NULL;
    
    // select the audio track
    if (BLT_SUCCEEDED(BLT_Stream_GetProperties(ATX_BASE(self, BLT_BaseMediaNode).context, &properties))) {
        ATX_PropertyValue value;
        bool selector_is_strict = false;
        if (ATX_SUCCEEDED(ATX_Properties_GetProperty(properties, 
                                                     BLT_STREAM_AUDIO_TRACK_SELECTOR_STRICT_PROPERTY, 
                                                     &value))) {
            if (value.type == ATX_PROPERTY_VALUE_TYPE_BOOLEAN) {
                selector_is_strict = value.data.boolean == ATX_TRUE;
            }
        }
        if (ATX_SUCCEEDED(ATX_Properties_GetProperty(properties, 
                                                     BLT_STREAM_AUDIO_TRACK_SELECTOR_INDEX_PROPERTY, 
                                                     &value))) {
            if (value.type == ATX_PROPERTY_VALUE_TYPE_INTEGER) {
                ATX_LOG_INFO_1("selecting audio track by index (%d)", value.data.integer);
                self->audio_output.track = movie->GetTrack(AP4_Track::TYPE_AUDIO, value.data.integer);
                if (self->audio_output.track == NULL) {
                    ATX_LOG_INFO("track not found");
                    if (selector_is_strict) return BLT_SUCCESS;
                }
            }
        } else if (ATX_SUCCEEDED(ATX_Properties_GetProperty(properties, 
                                                            BLT_STREAM_AUDIO_TRACK_SELECTOR_ID_PROPERTY, 
                                                            &value))) {
            if (value.type == ATX_PROPERTY_VALUE_TYPE_INTEGER) {
                ATX_LOG_INFO_1("selecting audio track by ID (%d)", value.data.integer);
                self->audio_output.track = movie->GetTrack((AP4_UI32)value.data.integer);
                if (self->audio_output.track == NULL) {
                    ATX_LOG_INFO("track not found");
                    if (selector_is_strict) return BLT_SUCCESS;
                } else if (self->audio_output.track->GetType() != AP4_Track::TYPE_AUDIO) {
                    ATX_LOG_INFO("track is not audio");
                    if (selector_is_strict) return BLT_SUCCESS;
                } 
            }
        }
    }
    
    // select the first audio track if no specific track was selected
    if (self->audio_output.track == NULL) {
        ATX_LOG_INFO("selecting first audio track");
        self->audio_output.track = movie->GetTrack(AP4_Track::TYPE_AUDIO);
    }
    
    // exit now if the track does not exist
    if (self->audio_output.track == NULL) return BLT_SUCCESS;

    ATX_LOG_INFO_1("found audio track (id=%d)", self->audio_output.track->GetId());
    
    // use the first sample description by default
    return Mp4ParserOutput_SetSampleDescription(&self->audio_output, 0);
}
예제 #2
0
/*----------------------------------------------------------------------
|    ATX_HttpMessage_GetBody
+---------------------------------------------------------------------*/
ATX_Result
ATX_HttpMessage_GetBody(const ATX_HttpMessage* self,
                        ATX_InputStream*       stream,
                        ATX_Size*              content_length)
{
    /* return a reference to the stream */
    if (stream) {
        stream = self->body;
        ATX_REFERENCE_OBJECT(stream);
    }

    /* return the content length */
    if (content_length) {
        const ATX_String* length_string = 
            ATX_HttpMessage_GetHeader(self, ATX_HTTP_HEADER_CONTENT_LENGTH);
        if (length_string && self->body) {
            int length = 0;
            if (ATX_SUCCEEDED(ATX_String_ToInteger(length_string, &length, ATX_TRUE))) {
                *content_length = (ATX_Size)length;
            }
        } else {
            *content_length = 0;
        }
    }

    return ATX_SUCCESS;
}
/*----------------------------------------------------------------------
|   BLT_DecoderServer::OnSetPropertyCommand
+---------------------------------------------------------------------*/
void 
BLT_DecoderServer::OnSetPropertyCommand(BLT_PropertyScope        scope,
                                        const NPT_String&        /*target*/,
                                        const NPT_String&        name,
                                        const ATX_PropertyValue* value)
{
    BLT_Result result;
    ATX_LOG_FINE_1("[%s]", name.GetChars());

    ATX_Properties* properties = NULL;
    switch (scope) {
        case BLT_PROPERTY_SCOPE_CORE:
            result = BLT_Decoder_GetProperties(m_Decoder, &properties);
            break;
            
        case BLT_PROPERTY_SCOPE_STREAM:
            result = BLT_Decoder_GetStreamProperties(m_Decoder, &properties);
            break;
            
        default:
            // not handled yet
            result = BLT_ERROR_NOT_SUPPORTED;
    }
    if (ATX_SUCCEEDED(result) && properties != NULL) {
        result = ATX_Properties_SetProperty(properties, name.GetChars(), value);
    }
    SendReply(BLT_DecoderServer_Message::COMMAND_ID_SET_PROPERTY, result);
}
예제 #4
0
/*----------------------------------------------------------------------
|   DcfParser_Construct
+---------------------------------------------------------------------*/
static void
DcfParser_Construct(DcfParser* self, BLT_Module* module, BLT_Core* core)
{
    /* construct the inherited object */
    BLT_BaseMediaNode_Construct(&ATX_BASE(self, BLT_BaseMediaNode), module, core);

    /* construct the members */
    DcfParserInput_Construct(&self->input, module);
    DcfParserOutput_Construct(&self->output);
    
    /* look for a key manager */
    ATX_Properties* properties = NULL;
    if (BLT_SUCCEEDED(BLT_Core_GetProperties(core, &properties))) {
        ATX_PropertyValue value;
        if (ATX_SUCCEEDED(ATX_Properties_GetProperty(properties, 
                                                     BLT_KEY_MANAGER_PROPERTY, 
                                                     &value))) {
            if (value.type == ATX_PROPERTY_VALUE_TYPE_POINTER) {
                self->key_manager = (BLT_KeyManager*)value.data.pointer;
            }
        } else {
            ATX_LOG_FINE("no key manager");
        }

        /* check if we need to use a cipher factory */
        if (ATX_SUCCEEDED(ATX_Properties_GetProperty(properties, 
                                                     BLT_CIPHER_FACTORY_PROPERTY, 
                                                     &value))) {
            if (value.type == ATX_PROPERTY_VALUE_TYPE_POINTER) {
                self->cipher_factory = new BLT_Ap4CipherFactoryAdapter((BLT_CipherFactory*)value.data.pointer);
            }
        } else {
            ATX_LOG_FINE("no cipher factory");
        }
    }

    /* setup interfaces */
    ATX_SET_INTERFACE_EX(self, DcfParser, BLT_BaseMediaNode, BLT_MediaNode);
    ATX_SET_INTERFACE_EX(self, DcfParser, BLT_BaseMediaNode, ATX_Referenceable);
    ATX_SET_INTERFACE(&self->input,  DcfParserInput,  BLT_MediaPort);
    ATX_SET_INTERFACE(&self->input,  DcfParserInput,  BLT_InputStreamUser);
    ATX_SET_INTERFACE(&self->output, DcfParserOutput, BLT_MediaPort);
    ATX_SET_INTERFACE(&self->output, DcfParserOutput, BLT_InputStreamProvider);
}
예제 #5
0
/*----------------------------------------------------------------------
|    SilenceRemover_AcceptPacket
+---------------------------------------------------------------------*/
static void
SilenceRemover_AcceptPacket(SilenceRemover* self, BLT_MediaPacket* packet)
{
    BLT_Result result;
    ATX_LOG_FINER("SilenceRemover: accepting packet");

    /* first, use any pending packet */
    SilenceRemover_AcceptPending(self);

    /* add the packet to the output list */
    result = ATX_List_AddData(self->output.packets, packet);
    if (ATX_SUCCEEDED(result)) {
        BLT_MediaPacket_AddReference(packet);
    }
}
예제 #6
0
/*----------------------------------------------------------------------
|   ATX_HttpClient_SendRequest
+---------------------------------------------------------------------*/
ATX_Result
ATX_HttpClient_SendRequest(ATX_HttpClient*    self, 
                           ATX_HttpRequest*   request, 
                           ATX_HttpResponse** response)
{
    ATX_Cardinal watchdog = ATX_HTTP_MAX_REDIRECTS;
    ATX_Boolean  keep_going;
    ATX_Result   result;

    do {
        keep_going = ATX_FALSE;
        result = ATX_HttpClient_SendRequestOnce(self, request, response);
        if (ATX_FAILED(result)) break;
        if (*response && self->options.follow_redirect &&
            (ATX_String_Equals(&request->method, ATX_HTTP_METHOD_GET, ATX_FALSE) ||
             ATX_String_Equals(&request->method, ATX_HTTP_METHOD_HEAD, ATX_FALSE)) &&
            ((*response)->status_code == 301 ||
             (*response)->status_code == 302 ||
             (*response)->status_code == 303 ||
             (*response)->status_code == 307)) {
            /* handle redirect */
            const ATX_String* location = 
                ATX_HttpMessage_GetHeader((ATX_HttpMessage*)*response,
                                          ATX_HTTP_HEADER_LOCATION);
            if (location) {
                /* replace the request url */
                ATX_HttpUrl url;
                result = ATX_HttpUrl_Construct(&url, ATX_String_GetChars(location));
                if (ATX_SUCCEEDED(result)) {
                    ATX_LOG_FINE_1("ATX_HttpClient::SendRequest - redirecting to %s",
                                   ATX_String_GetChars(location));
                    ATX_HttpUrl_Destruct(&request->url);
                    request->url = url;
                    keep_going = ATX_TRUE;
                    ATX_HttpResponse_Destroy(*response);
                    *response = NULL;
                } else {
                    ATX_LOG_FINE_1("ATX_HttpClient::SendRequest - failed to create redirection URL (%d)", result);
                    break;
                }
            }
        }       
    } while (keep_going && watchdog--);

    return result;
}
예제 #7
0
/*----------------------------------------------------------------------
|       DumpProperties
+---------------------------------------------------------------------*/
static void
DumpProperties(ATX_Properties* properties)
{
    ATX_Iterator* iterator;
    ATX_Property* property;

    ATX_Debug("[PROPERTIES] -------------------------------\n");
    if (ATX_FAILED(ATX_Properties_GetIterator(properties, &iterator))) {
        return;
    }
    while (ATX_SUCCEEDED(ATX_Iterator_GetNext(iterator, 
                                              (ATX_Any*)(void*)&property))) {
        PrintProperty(property->name, property->type, &property->value);
    }
    ATX_Debug("--------------------------------------------\n");

    ATX_DESTROY_OBJECT(iterator);
}
예제 #8
0
/*----------------------------------------------------------------------
|   ATX_HttpClient_SendRequest
+---------------------------------------------------------------------*/
ATX_Result
ATX_HttpClient_SendRequest(ATX_HttpClient*    self, 
                           ATX_HttpRequest*   request, 
                           ATX_HttpResponse** response)
{
    ATX_Cardinal watchdog = ATX_HTTP_MAX_REDIRECTS;
    ATX_Boolean  keep_going;
    ATX_Result   result;

    do {
        keep_going = ATX_FALSE;
        result = ATX_HttpClient_SendRequestOnce(self, request, response);
        if (ATX_FAILED(result)) break;
        if (*response && 
            self->options.follow_redirect &&
            ((*response)->status_code == 301 ||
             (*response)->status_code == 302 ||
             (*response)->status_code == 303 ||
             (*response)->status_code == 307)) {
            /* handle redirect */
            const ATX_String* location = 
                ATX_HttpMessage_GetHeader((ATX_HttpMessage*)*response,
                                          ATX_HTTP_HEADER_LOCATION);
            if (location) {
                /* replace the request url */
                ATX_HttpUrl url;
                result = ATX_HttpUrl_Construct(&url, ATX_String_GetChars(location));
                if (ATX_SUCCEEDED(result)) {
                    ATX_HttpUrl_Destruct(&request->url);
                    request->url = url;
                    keep_going = ATX_TRUE;

                    ATX_Debug("ATX_HttpClient::SendRequest - redirecting to %s\n",
                              ATX_String_GetChars(location));
                }
            }
        }       
    } while (keep_going && watchdog--);

    return result;
}
/*----------------------------------------------------------------------
|    Test
+---------------------------------------------------------------------*/
static void
Test(int buffer_size, int source_size)
{
    ATX_MemoryStream* memory;
    ATX_InputStream*  source;
    ATX_OutputStream* source_buffer;
    ATX_InputStream*  stream;
    ATX_Offset        offset = 0;
    int               i; 
    unsigned char     scratch[4096];
    ATX_Boolean       expect_eos = ATX_FALSE;
    ATX_Result        result;

    /* create and setup the source */
    ATX_MemoryStream_Create(source_size, &memory);
    ATX_MemoryStream_GetOutputStream(memory, &source_buffer);
    ATX_MemoryStream_GetInputStream(memory, &source);
    for (i=0; i<source_size; i++) {
        unsigned char x = (unsigned char)i;
        ATX_OutputStream_Write(source_buffer, &x, 1, NULL);        
    }
    ATX_OutputStream_Seek(source_buffer, 0);
    ATX_RELEASE_OBJECT(source_buffer);
    ATX_MemoryStream_Destroy(memory);

    /* create the stream */
    BLT_NetworkStream_Create(buffer_size, source, &stream);
    offset = 0;
    for (;;) {
        ATX_Size bytes_read = 0;
        ATX_Size chunk;
        if ((ATX_System_GetRandomInteger()%7) == 0) {
            chunk = ATX_System_GetRandomInteger()%(10+source_size);
        } else {
            chunk = ATX_System_GetRandomInteger()%7;
        }
        if ((ATX_System_GetRandomInteger()%5) == 0) {
            ATX_Position position = ATX_System_GetRandomInteger()%(10+source_size);
            ATX_Position new_position;
            ATX_Result   result = ATX_InputStream_Seek(stream, position);
            if (ATX_SUCCEEDED(result)) {
                ATX_InputStream_Tell(stream, &new_position);
                CHECK(new_position == position);
                expect_eos = ATX_FALSE;
            } else {
                result = ATX_InputStream_Seek(stream, offset);
                CHECK(result == ATX_SUCCESS);
                ATX_InputStream_Tell(stream, &new_position);
                CHECK(new_position == (ATX_Position)offset);
            }
            offset = new_position;
        }
        ATX_SetMemory(scratch, 0, chunk);
        result = ATX_InputStream_Read(stream, scratch, chunk, &bytes_read);
        if (ATX_FAILED(result)) {
            CHECK(result == ATX_ERROR_EOS);
            CHECK(offset == source_size);
            break;
        }
        CHECK(chunk == 0 || expect_eos == ATX_FALSE);
        CHECK(bytes_read <= chunk);
        if (bytes_read != chunk) {
            expect_eos = ATX_TRUE;
        }
        {
            unsigned int j;
            for (j=0; j<bytes_read; j++) {
                CHECK(scratch[j] == (unsigned char)(offset+j));
            }
        }
        offset += bytes_read;
    }

    ATX_RELEASE_OBJECT(stream);
    ATX_RELEASE_OBJECT(source);
}
예제 #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;
}
예제 #11
0
/*----------------------------------------------------------------------
|   Mp4ParserInput_SetStream
+---------------------------------------------------------------------*/
BLT_METHOD
Mp4ParserInput_SetStream(BLT_InputStreamUser* _self,
                         ATX_InputStream*     stream,
                         const BLT_MediaType* stream_media_type)
{
    Mp4Parser* self = ATX_SELF_M(input, Mp4Parser, BLT_InputStreamUser);
    BLT_Result result = BLT_ERROR_INVALID_MEDIA_FORMAT;
    
    /* check media type */
    if (stream_media_type == NULL || 
        (stream_media_type->id != self->input.audio_media_type.id &&
         stream_media_type->id != self->input.video_media_type.id)) {
        return BLT_ERROR_INVALID_MEDIA_TYPE;
    }

    /* if we had a file before, release it now */
    delete self->input.mp4_file;
    self->input.mp4_file = NULL;
    self->input.slow_seek = false;
    
    /* create an adapter for the stream */
    AP4_ByteStream* stream_adapter = new ATX_InputStream_To_AP4_ByteStream_Adapter(stream);

    /* check if the source can seek quickly or not */
    {
        ATX_Properties* stream_properties = ATX_CAST(stream, ATX_Properties);
        if (stream_properties) {
            ATX_PropertyValue property_value;
            result = ATX_Properties_GetProperty(stream_properties, 
                                                ATX_INPUT_STREAM_PROPERTY_SEEK_SPEED,
                                                &property_value);
            if (ATX_SUCCEEDED(result) && 
                property_value.type == ATX_PROPERTY_VALUE_TYPE_INTEGER &&
                property_value.data.integer <= ATX_INPUT_STREAM_SEEK_SPEED_SLOW) {
                AP4_ByteStream* buffered = new AP4_BufferedInputStream(*stream_adapter);
                ATX_LOG_FINE("using no-seek mode, source is slow");
                stream_adapter->Release();
                stream_adapter = buffered;
                self->input.slow_seek = true;
            }
        }
    }

    /* parse the MP4 file */
    ATX_LOG_FINE("parsing MP4 file");
    self->input.mp4_file = new AP4_File(*stream_adapter, 
                                        AP4_DefaultAtomFactory::Instance,
                                        true); /* parse until moov only */
    stream_adapter->Release();

    // get the global file info
    AP4_Movie* movie = self->input.mp4_file->GetMovie();
    if (movie == NULL) {
        ATX_LOG_FINE("no movie in file");
        goto fail;
    }
    
    // update the stream info
    BLT_StreamInfo stream_info;
    stream_info.type     = BLT_STREAM_TYPE_MULTIPLEXED;
    stream_info.id       = 0;
    stream_info.duration = movie->GetDurationMs();
    stream_info.mask = BLT_STREAM_INFO_MASK_TYPE |
                       BLT_STREAM_INFO_MASK_ID   |
                       BLT_STREAM_INFO_MASK_DURATION;    
    BLT_Stream_SetInfo(ATX_BASE(self, BLT_BaseMediaNode).context, &stream_info);
    
    // create a linear reader if the source is slow-seeking
    if (self->input.slow_seek) {
        self->input.reader = new AP4_LinearReader(*movie);
    }
    
    // setup the tracks
    result = Mp4Parser_SetupAudioOutput(self, movie);
    if (BLT_FAILED(result)) goto fail;
    result = Mp4Parser_SetupVideoOutput(self, movie);
    if (BLT_FAILED(result)) goto fail;
    
    // check that we have at least one media track
    if (self->audio_output.track == NULL && 
        self->video_output.track == NULL) {
        ATX_LOG_FINE("no media track found");
        goto fail;
    }
    
    return BLT_SUCCESS;

fail:
    delete self->input.mp4_file;
    self->input.mp4_file = NULL;
    self->audio_output.track = NULL;
    self->video_output.track = NULL;
    
    return result;
}
예제 #12
0
/*----------------------------------------------------------------------
|   Mp4ParserOutput_ProcessCryptoInfo
+---------------------------------------------------------------------*/
static BLT_Result
Mp4ParserOutput_ProcessCryptoInfo(Mp4ParserOutput*        self, 
                                  AP4_SampleDescription*& sample_desc)
{
    // check if the track is encrypted
    if (sample_desc->GetType() == AP4_SampleDescription::TYPE_PROTECTED) {
        ATX_LOG_FINE("track is encrypted");
        AP4_ProtectedSampleDescription* prot_desc = dynamic_cast<AP4_ProtectedSampleDescription*>(sample_desc);
        if (prot_desc == NULL) {
            ATX_LOG_FINE("unable to obtain cipher info");
            return BLT_ERROR_INVALID_MEDIA_FORMAT;
        }

        // obtain the key manager
        if (self->parser->key_manager == NULL) {
            ATX_Properties* properties = NULL;
            if (BLT_SUCCEEDED(BLT_Core_GetProperties(ATX_BASE(self->parser, BLT_BaseMediaNode).core, 
                                                              &properties))) {
                ATX_PropertyValue value;
                if (ATX_SUCCEEDED(ATX_Properties_GetProperty(properties, 
                                                             BLT_KEY_MANAGER_PROPERTY, 
                                                             &value))) {
                    if (value.type == ATX_PROPERTY_VALUE_TYPE_POINTER) {
                        self->parser->key_manager = (BLT_KeyManager*)value.data.pointer;
                    }
                } else {
                    ATX_LOG_FINE("no key manager");
                }
            }
        }
        if (self->parser->key_manager == NULL) return BLT_ERROR_NO_MEDIA_KEY;
        
        // check if we need to use a cipher factory
        if (self->parser->cipher_factory == NULL) {
            ATX_Properties* properties = NULL;
            if (BLT_SUCCEEDED(BLT_Core_GetProperties(ATX_BASE(self->parser, BLT_BaseMediaNode).core, 
                                                              &properties))) {
                ATX_PropertyValue value;
                if (ATX_SUCCEEDED(ATX_Properties_GetProperty(properties, 
                                                             BLT_CIPHER_FACTORY_PROPERTY, 
                                                             &value))) {
                    if (value.type == ATX_PROPERTY_VALUE_TYPE_POINTER) {
                        self->parser->cipher_factory = new BLT_Ap4CipherFactoryAdapter((BLT_CipherFactory*)value.data.pointer);
                    }
                }
            }
        }
        
        // figure out the content ID for this track
        // TODO: support different content ID schemes
        // for now, we just make up a content ID based on the track ID
        char content_id[32];
        NPT_FormatString(content_id, sizeof(content_id), "@track.%d", self->track->GetId());
        
        // get the key for this content
        unsigned int   key_size = 256;
        NPT_DataBuffer key(key_size);
        BLT_Result result = BLT_KeyManager_GetKeyByName(self->parser->key_manager, content_id, key.UseData(), &key_size);
        if (result == ATX_ERROR_NOT_ENOUGH_SPACE) {
            key.SetDataSize(key_size);
            result = BLT_KeyManager_GetKeyByName(self->parser->key_manager, content_id, key.UseData(), &key_size);
        }
        if (BLT_FAILED(result)) return BLT_ERROR_NO_MEDIA_KEY;
        key.SetDataSize(key_size);
        
        delete self->sample_decrypter;
        self->sample_decrypter = AP4_SampleDecrypter::Create(prot_desc, key.GetData(), key_size, self->parser->cipher_factory);
        if (self->sample_decrypter == NULL) {
            ATX_LOG_FINE("unable to create decrypter");
            return BLT_ERROR_CRYPTO_FAILURE;
        }
        
        // switch to the original sample description
        sample_desc = prot_desc->GetOriginalSampleDescription();
    }
    
    return BLT_SUCCESS;
}