/*---------------------------------------------------------------------- | 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); }
/*---------------------------------------------------------------------- | 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); }
/*---------------------------------------------------------------------- | 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); }
/*---------------------------------------------------------------------- | 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); } }
/*---------------------------------------------------------------------- | 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; }
/*---------------------------------------------------------------------- | 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); }
/*---------------------------------------------------------------------- | 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); }
/*---------------------------------------------------------------------- | 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; }
/*---------------------------------------------------------------------- | 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; }
/*---------------------------------------------------------------------- | 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; }