diff options
-rw-r--r-- | Makefile | 3 | ||||
-rw-r--r-- | README | 14 | ||||
-rw-r--r-- | common.h | 81 | ||||
-rw-r--r-- | database/object.cpp | 4 | ||||
-rw-r--r-- | database/object.h | 12 | ||||
-rw-r--r-- | database/resources.cpp | 115 | ||||
-rw-r--r-- | misc/avdetector.cpp | 250 | ||||
-rw-r--r-- | misc/avdetector.h | 82 | ||||
-rw-r--r-- | misc/util.cpp | 44 | ||||
-rw-r--r-- | misc/util.h | 2 | ||||
-rw-r--r-- | receiver/recplayer.cpp | 4 | ||||
-rw-r--r-- | upnpcomponents/dlna.cpp | 26 | ||||
-rw-r--r-- | upnpcomponents/dlna.h | 33 | ||||
-rw-r--r-- | upnpcomponents/upnpwebserver.cpp | 4 |
14 files changed, 453 insertions, 221 deletions
@@ -52,6 +52,7 @@ LIBS += -lupnp -lixml -lsqlite3 -lavformat -lavcodec INCLUDES += -I$(VDRDIR)/include -I/usr/include \ DEFINES += -D_GNU_SOURCE -DPLUGIN_NAME_I18N='"$(PLUGIN)"' +DEFINES += -D_FILE_OFFSET_BITS=64 -D_LARGEFILE_SOURCE -D_LARGEFILE64_SOURCE ### The object files (add further files here): @@ -62,12 +63,12 @@ OBJS = $(PLUGIN).o \ misc/util.o \ misc/config.o \ misc/search.o \ + misc/avdetector.o \ database/database.o \ database/metadata.o \ database/object.o \ database/resources.o \ server/server.o \ - misc/avdetector.o \ upnpcomponents/dlna.o \ upnpcomponents/upnpwebserver.o \ upnpcomponents/upnpservice.o \ @@ -21,7 +21,7 @@ University of Applied Science Gustav-Freytag-Straße 43-45 04277 Leipzig Germany -http://www.hftl.de +http://www.hft-leipzig.de All rights reserved. @@ -83,9 +83,9 @@ The server has a unique identifier, which is "uuid:b120ba52-d88d-4500-9b64-888971d83fd3". Other devices in the network can find and identify the VDR UPnP Server with this ID. However, the server should be found automatically and being listed under the supported media server -devices. If not, please report this as a bug on the projects homepage or send an +devices. If not, please report this as a bug on the project homepage or send an email to the developers of this plugin with the full device description and, if -applicable, the errors thrown by the media player device and the server. +applicable, the errors thrown by the media player device and/or the server. Dependencies: @@ -106,3 +106,11 @@ ATTENTION: DO NOT USE ANOTHER VERSION OF LIBUPNP. libupnp-1.8.0 is known not to work with this plugin! Versions below 1.6.6 may work. However, there may exist some unknown issues. + +If you want to know everything about the code, please see the source code +documentation at http://upnp.vdr-developer.org/docs/ where all public members +are explained. + +I appreciate, if you help making the plugin better. If you find any project +related errors or missing features, please open a new ticket at the plugin +website with a detailed description of what you want or what happened. @@ -66,6 +66,7 @@ #define INOUT //#define DEBUG +//#define WITH_WINDOWS_MEDIA #define TOSTRING(s) #s @@ -177,24 +178,24 @@ void message(int level, const char* File, int Line, const char* Format, ...) __a * - \-vvvvv Log level 4 * - \-vvvvvv Log level 5 */ -#define VERBOSE_SQL 4 -#define VERBOSE_SQL_FETCHES 5 -#define VERBOSE_SQL_STATEMENTS 5 -#define VERBOSE_DIDL 4 -#define VERBOSE_LIVE_TV 3 -#define VERBOSE_RECORDS 3 -#define VERBOSE_CUSTOMFILES 3 -#define VERBOSE_SDK 1 -#define VERBOSE_EPG_UPDATES 3 -#define VERBOSE_WEBSERVER 2 -#define VERBOSE_MODIFICATIONS 2 -#define VERBOSE_METADATA 3 -#define VERBOSE_CUSTOM_OUTPUT 5 -#define VERBOSE_PARSERS 5 -#define VERBOSE_BUFFERS 5 -#define VERBOSE_CDS 2 -#define VERBOSE_CMS 2 -#define VERBOSE_OBJECTS 3 +#define VERBOSE_SQL 4 ///< SQL messages (open DB, close DB etc.) +#define VERBOSE_SQL_FETCHES 0 ///< SQL fetch messages (SELECT only) +#define VERBOSE_SQL_STATEMENTS 0 ///< all SQL statements +#define VERBOSE_DIDL 4 ///< print DIDL related messages +#define VERBOSE_LIVE_TV 3 ///< print live TV related messages +#define VERBOSE_RECORDS 3 ///< print messages related to records +#define VERBOSE_CUSTOMFILES 3 ///< print messages related to custom files and sometimes records +#define VERBOSE_SDK 1 ///< print important messages of the program flow +#define VERBOSE_EPG_UPDATES 3 ///< show information on EPG changes +#define VERBOSE_WEBSERVER 2 ///< print actions done by the webserver +#define VERBOSE_MODIFICATIONS 2 ///< show modifications to objects or anything else +#define VERBOSE_METADATA 3 ///< print additional metadata information +#define VERBOSE_CUSTOM_OUTPUT 5 ///< everything else... +#define VERBOSE_PARSERS 5 ///< print the parsers output +#define VERBOSE_BUFFERS 5 ///< print the buffer output +#define VERBOSE_CDS 2 ///< print messages from the content directory +#define VERBOSE_CMS 2 ///< print messages from the connection manager +#define VERBOSE_OBJECTS 3 ///< print messages related to objects /**************************************************** * @@ -468,6 +469,10 @@ enum UPNP_WEB_METHODS { UPNP_PROP_LONGDESCRIPTION ","\ UPNP_PROP_PUBLISHER +#define UPNP_DURATION_FORMAT_STRING "%5d:%02d:%02d" +#define UPNP_DURATION_FRAME_FORMAT ".%03d" +#define AVDETECTOR_TIME_BASE 1000 + /**************************************************** * * The UPnP CDS actions @@ -731,45 +736,7 @@ enum UPnPWriteStatus { * ****************************************************/ -/** - * The combination of DLNA profile ID and the corresponding mime type - * - * This complies with the DLNA media format guidelines. Though this is very - * similar to the profile structure of libdlna, it comes without the additional - * label field as it seams to be not needed. - */ -struct DLNAProfile { - const char* ID; ///< the DLNA profile ID - const char* mime; ///< the mime type of the resource -}; - -/** - * The DLNA profile for a icon image - * - * This complies with the DLNA media format guidelines. It contains a valid - * mime type, the resolution of the image and the corresponding bit depth - */ -struct DLNAIconProfile { - const char* mime; ///< the mime type of the image - unsigned short width; ///< image width in pixel - unsigned short height; ///< image height in pixel - unsigned char bitDepth; ///< bit depth in bits per pixel -}; - -/* Images */ -/* Audio */ -extern DLNAProfile DLNA_PROFILE_MPEG1_L3; ///< DLNA MP3 Profile -/* Video */ -extern DLNAProfile DLNA_PROFILE_MPEG_TS_SD_EU; ///< DLNA Profile for DVB Television broadcasts -extern DLNAProfile DLNA_PROFILE_AVC_TS_HD_EU; ///< DLNA Profile for HD DVB Television broadcasts -extern DLNAProfile DLNA_PROFILE_MPEG_TS_SD_EU_ISO; ///< DLNA Profile for DVB Television broadcasts without timestamp -extern DLNAProfile DLNA_PROFILE_AVC_TS_HD_EU_ISO; ///< DLNA Profile for HD DVB Television broadcasts without timestamp - -/* Icons */ -extern DLNAIconProfile DLNA_ICON_JPEG_SM_24; ///< DLNA icon profile of small jpeg images -extern DLNAIconProfile DLNA_ICON_JPEG_LRG_24; ///< DLNA icon profile of large jpeg images -extern DLNAIconProfile DLNA_ICON_PNG_SM_24A; ///< DLNA icon profile of small png images -extern DLNAIconProfile DLNA_ICON_PNG_LRG_24A; ///< DLNA icon profile of large png images +/** @see dlna/profiles.h */ /**************************************************** * diff --git a/database/object.cpp b/database/object.cpp index f6875f9..5ab355d 100644 --- a/database/object.cpp +++ b/database/object.cpp @@ -35,10 +35,6 @@ cUPnPResource::cUPnPResource(){ this->mContentType = NULL; } -off64_t cUPnPResource::getFileSize() const { - return (this->mSize) ? this->mSize : (off64_t)-1; -} - time_t cUPnPResource::getLastModification() const { time_t Time; const cRecording* Recording; diff --git a/database/object.h b/database/object.h index 245a617..9c2c133 100644 --- a/database/object.h +++ b/database/object.h @@ -137,7 +137,7 @@ private: cString mProtocolInfo; cString mContentType; cString mImportURI; - unsigned long mSize; + off64_t mSize; unsigned int mBitrate; unsigned int mSampleFrequency; unsigned int mBitsPerSample; @@ -212,14 +212,6 @@ public: */ int getResourceType() const { return this->mResourceType; } /** - * Get the size - * - * Returns the resource size or -1 if its unknown - * - * @return the resource size or -1 if unknown - */ - unsigned long getSize() const { return this->mSize; } - /** * Get the file size * * Returns the file size in bytes of the resource or 0 if its unknown or a @@ -227,7 +219,7 @@ public: * * @return the file size */ - off64_t getFileSize() const; + off64_t getFileSize() const { return this->mSize; }; /** * Get the last modification * diff --git a/database/resources.cpp b/database/resources.cpp index 8f0dec4..646560a 100644 --- a/database/resources.cpp +++ b/database/resources.cpp @@ -90,24 +90,45 @@ cUPnPResource* cUPnPResources::getResource(unsigned int ResourceID){ } int cUPnPResources::createFromRecording(cUPnPClassVideoItem* Object, cRecording* Recording){ - cString VideoFile = cString::sprintf(VDR_RECORDFILE_PATTERN_TS, Recording->FileName(), 1); - - cAudioVideoDetector* Detector = new cAudioVideoDetector(); - - // TODO: add DLNA-Support to detector - - cString ContentType = "video/mpeg"; - cString ProtocolInfo = "http-get:*:video/mpeg:*"; + if(!Object || !Recording){ + ERROR("Invalid input arguments"); + return -1; + } - cUPnPResource* Resource = this->mMediator->newResource(Object, UPNP_RESOURCE_RECORDING, Recording->FileName(), ContentType, ProtocolInfo); + cAudioVideoDetector* Detector = new cAudioVideoDetector(Recording); - if(Detector->detectVideoProperties(Resource, VideoFile)){ + if(Detector->detectProperties()){ ERROR("Error while detecting video properties"); + delete Detector; return -1; } + if(!Detector->getDLNAProfile()){ + ERROR("No DLNA profile found for Recording %s", Recording->Name()); + delete Detector; + return -1; + } + + const char* ProtocolInfo = cDlna::getInstance()->getProtocolInfo(Detector->getDLNAProfile()); + + MESSAGE(VERBOSE_METADATA, "Protocol info: %s", ProtocolInfo); + + cString ResourceFile = Recording->FileName(); + cUPnPResource* Resource = this->mMediator->newResource(Object, UPNP_RESOURCE_RECORDING,ResourceFile, Detector->getDLNAProfile()->mime, ProtocolInfo); + Resource->mBitrate = Detector->getBitrate(); + Resource->mBitsPerSample = Detector->getBitsPerSample(); + Resource->mDuration = duration(Detector->getDuration()); + Resource->mResolution = (Detector->getWidth() && Detector->getHeight()) ? *cString::sprintf("%dx%d",Detector->getWidth(), Detector->getHeight()) : NULL; + Resource->mSampleFrequency = Detector->getSampleFrequency(); + Resource->mSize = Detector->getFileSize(); + Resource->mNrAudioChannels = Detector->getNumberOfAudioChannels(); + Resource->mImportURI = NULL; + Resource->mColorDepth = 0; + Object->addResource(Resource); + this->mMediator->saveResource(Resource); + this->mResources->Add(Resource, Resource->getID()); + delete Detector; - MESSAGE(VERBOSE_SDK, "To be continued, may it work with DLNA?! Guess, not!"); return 0; } @@ -122,53 +143,45 @@ int cUPnPResources::createFromChannel(cUPnPClassVideoBroadcast* Object, cChannel return -1; } - DLNAProfile* Profile = cDlna::getInstance()->getProfileOfChannel(Channel); + cAudioVideoDetector* Detector = new cAudioVideoDetector(Channel); + + if(!Detector->detectProperties()){ + ERROR("Cannot detect channel properties"); + delete Detector; + return -1; + } - if(!Profile){ - ERROR("No profile found for Channel %s", *Channel->GetChannelID().ToString()); + if(!Detector->getDLNAProfile()){ + ERROR("No DLNA profile found for Channel %s", *Channel->GetChannelID().ToString()); + delete Detector; return -1; } - const char* ProtocolInfo = cDlna::getInstance()->getProtocolInfo(Profile); + const char* ProtocolInfo = cDlna::getInstance()->getProtocolInfo(Detector->getDLNAProfile()); MESSAGE(VERBOSE_METADATA, "Protocol info: %s", ProtocolInfo); - // Adapted from streamdev + // Index which may be used to indicate different resources with same channel ID + // For instance a different DVB card + // Not used yet. int index = 0; - for(int i=0; Channel->Apid(i)!=0; i++, index++){ - MESSAGE(VERBOSE_METADATA, "Analog channel %d", i); - cString ResourceFile = cString::sprintf("%s:%d", *Channel->GetChannelID().ToString(), index); - cUPnPResource* Resource = this->mMediator->newResource(Object, UPNP_RESOURCE_CHANNEL,ResourceFile, Profile->mime, ProtocolInfo); - Resource->mBitrate = 0; - Resource->mBitsPerSample = 0; - Resource->mColorDepth = 0; - Resource->mDuration = NULL; - Resource->mImportURI = NULL; - Resource->mResolution = NULL; - Resource->mSampleFrequency = 0; - Resource->mSize = 0; - Resource->mNrAudioChannels = 0; - Object->addResource(Resource); - this->mMediator->saveResource(Resource); - this->mResources->Add(Resource, Resource->getID()); - } - for(int i=0; Channel->Dpid(i)!=0; i++, index++){ - MESSAGE(VERBOSE_METADATA, "Digital channel %d", i); - cString ResourceFile = cString::sprintf("%s:%d", *Channel->GetChannelID().ToString(), index); - cUPnPResource* Resource = this->mMediator->newResource(Object, UPNP_RESOURCE_CHANNEL,ResourceFile, Profile->mime, ProtocolInfo); - Resource->mBitrate = 0; - Resource->mBitsPerSample = 0; - Resource->mColorDepth = 0; - Resource->mDuration = NULL; - Resource->mImportURI = NULL; - Resource->mResolution = NULL; - Resource->mSampleFrequency = 0; - Resource->mSize = 0; - Object->addResource(Resource); - this->mMediator->saveResource(Resource); - this->mResources->Add(Resource, Resource->getID()); - } + + cString ResourceFile = cString::sprintf("%s:%d", *Channel->GetChannelID().ToString(), index); + cUPnPResource* Resource = this->mMediator->newResource(Object, UPNP_RESOURCE_CHANNEL,ResourceFile, Detector->getDLNAProfile()->mime, ProtocolInfo); + Resource->mBitrate = Detector->getBitrate(); + Resource->mBitsPerSample = Detector->getBitsPerSample(); + Resource->mDuration = duration(Detector->getDuration()); + Resource->mResolution = (Detector->getWidth() && Detector->getHeight()) ? *cString::sprintf("%dx%d",Detector->getWidth(), Detector->getHeight()) : NULL; + Resource->mSampleFrequency = Detector->getSampleFrequency(); + Resource->mSize = Detector->getFileSize(); + Resource->mNrAudioChannels = Detector->getNumberOfAudioChannels(); + Resource->mImportURI = NULL; + Resource->mColorDepth = 0; + Object->addResource(Resource); + this->mMediator->saveResource(Resource); + this->mResources->Add(Resource, Resource->getID()); + delete Detector; return 0; } @@ -205,7 +218,7 @@ cUPnPResource* cUPnPResourceMediator::getResource(unsigned int ResourceID){ Resource->mResource = Value; } else if(!strcasecmp(SQLITE_COL_SIZE, Column)){ - Resource->mSize = *Value?atol(Value):0; + Resource->mSize = (off64_t)(*Value?atol(Value):0); } else if(!strcasecmp(SQLITE_COL_DURATION, Column)){ Resource->mDuration = Value; @@ -243,7 +256,7 @@ int cUPnPResourceMediator::saveResource(cUPnPResource* Resource){ cString Format = "UPDATE %s SET %s=%Q," "%s=%Q," "%s=%Q," - "%s=%ld," + "%s=%lld," "%s=%Q," "%s=%d," "%s=%d," diff --git a/misc/avdetector.cpp b/misc/avdetector.cpp index 8f3bc8f..bb1c9c8 100644 --- a/misc/avdetector.cpp +++ b/misc/avdetector.cpp @@ -6,31 +6,207 @@ */ #include "avdetector.h" -extern "C" { -#include <libavformat/avformat.h> -#include <libavcodec/avcodec.h> +#include <sys/stat.h> + +cAudioVideoDetector::cAudioVideoDetector(const char* Filename) : mResourceType(UPNP_RESOURCE_FILE) { + this->mResource.Filename = Filename; + this->init(); } -using namespace std; +cAudioVideoDetector::cAudioVideoDetector(const cChannel* Channel) : mResourceType(UPNP_RESOURCE_CHANNEL) { + this->mResource.Channel = Channel; + this->init(); +} -int cAudioVideoDetector::detectVideoProperties(cUPnPResource* Resource, const char* Filename){ -// // Register avformat +cAudioVideoDetector::cAudioVideoDetector(const cRecording* Recording) : mResourceType(UPNP_RESOURCE_RECORDING) { + this->mResource.Recording = Recording; + this->init(); +} + +cAudioVideoDetector::~cAudioVideoDetector(){ + this->uninit(); +} + +void cAudioVideoDetector::init(){ + this->mBitrate = 0; + this->mBitsPerSample = 0; + this->mColorDepth = 0; + this->mDLNAProfile = NULL; + this->mDuration = 0; + this->mHeight = 0; + this->mNrAudioChannels = 0; + this->mSampleFrequency = 0; + this->mSize = 0; + this->mWidth = 0; +} + +void cAudioVideoDetector::uninit(){ + this->init(); +} + +int cAudioVideoDetector::detectProperties(){ + int ret = 0; + switch(this->mResourceType){ + case UPNP_RESOURCE_CHANNEL: + ret = this->detectChannelProperties(); + break; + case UPNP_RESOURCE_RECORDING: + ret = this->detectRecordingProperties(); + break; + case UPNP_RESOURCE_FILE: + ret = this->detectFileProperties(); + break; + default: + WARNING("This resource type is not yet implemented."); + ret = -1; + break; + } + + return ret; +} + +int cAudioVideoDetector::detectChannelProperties(){ + this->mBitrate = 0; + this->mBitsPerSample = 0; + this->mColorDepth = 0; + this->mDuration = 0; + this->mHeight = 0; + this->mNrAudioChannels = 0; + this->mSampleFrequency = 0; + this->mSize = (off64_t)-1; + this->mWidth = 0; + + switch(this->mResource.Channel->Vtype()){ + case 0x02: + // MPEG2 Video + this->mDLNAProfile = &DLNA_PROFILE_MPEG_TS_SD_EU_ISO; + break; + case 0x1B: + this->mDLNAProfile = &DLNA_PROFILE_AVC_TS_HD_EU_ISO; + break; + default: + ERROR("Unknown video type %d for channel %s!", this->mResource.Channel->Vtype(), this->mResource.Channel->Name()); + this->mDLNAProfile = NULL; + return -1; + } + + return 0; +} + +int cAudioVideoDetector::detectRecordingProperties(){ + + if(this->mResource.Recording->IsPesRecording()){ + ERROR("Sorry, PES Recordings are not supported"); + return -1; + } + + int ret = 0; + AVFormatContext *FormatCtx = NULL; + + cIndexFile* Index = new cIndexFile(this->mResource.Recording->FileName(), false, this->mResource.Recording->IsPesRecording()); + cFileName* RecFile = new cFileName(this->mResource.Recording->FileName(), false, false, this->mResource.Recording->IsPesRecording()); + if(Index && Index->Ok()){ + this->mDuration = (off64_t) (Index->Last() * AVDETECTOR_TIME_BASE / SecondsToFrames(1, this->mResource.Recording->FramesPerSecond())); + MESSAGE(VERBOSE_METADATA,"Record length: %llds", this->mDuration); + + uint16_t FileNumber = 0; + off_t FileOffset = 0; + + if(Index->Get(Index->Last()-1, &FileNumber, &FileOffset)) + for(int i = 0; i < FileNumber; i++){ + struct stat Stats; + RecFile->SetOffset(i+1); + stat(RecFile->Name(),&Stats); + this->mSize += (off64_t) Stats.st_size; + } + + av_register_all(); + + if(!(ret = av_open_input_file(&FormatCtx, RecFile->Name(), NULL, 0, NULL))){ + if((ret = av_find_stream_info(FormatCtx))<0){ + ERROR("AVDetector: Cannot find the stream information"); + } + else { + if((ret = this->analyseVideo(FormatCtx))<0){ + ERROR("AVDetector: Error while analysing video"); + } + if((ret = this->analyseAudio(FormatCtx))<0){ + ERROR("AVDetector: Error while analysing audio"); + } + } + } + } + else { + ret = -1; + } + + if(ret != 0){ + ERROR("Error occured while detecting properties"); + } + + delete RecFile; + delete Index; + av_free(FormatCtx); + + return ret; +} + +int cAudioVideoDetector::detectFileProperties(){ av_register_all(); + int ret = 0; + AVFormatContext *FormatCtx = NULL; - if(av_open_input_file(&FormatCtx, Filename, NULL, 0, NULL)){ - ERROR("AVDetector: Error while opening file %s", Filename); + + if(av_open_input_file(&FormatCtx, this->mResource.Filename, NULL, 0, NULL)){ + ERROR("AVDetector: Error while opening file %s", this->mResource.Filename); return -1; } + else { + if(av_find_stream_info(FormatCtx)<0){ + ERROR("AVDetector: Cannot find the stream information"); + return -1; + } + else { - if(av_find_stream_info(FormatCtx)<0){ - ERROR("AVDetector: Cannot find the stream information"); - return -1; + if((ret = this->analyseFormat(FormatCtx))<0){ + ERROR("AVDetector: Error while analysing format"); + return ret; + } + if((ret = this->analyseVideo(FormatCtx))<0){ + ERROR("AVDetector: Error while analysing video"); + return ret; + } + if((ret = this->analyseAudio(FormatCtx))<0){ + ERROR("AVDetector: Error while analysing audio"); + return ret; + } + + return 0; + } } +} -#ifdef DEBUG - dump_format(FormatCtx, 0, Filename, 0); -#endif +int cAudioVideoDetector::analyseFormat(AVFormatContext* FormatCtx){ + if(!FormatCtx) return -1; + + this->mSize = FormatCtx->file_size; + this->mDuration = FormatCtx->duration; + + MESSAGE(VERBOSE_METADATA, "Format properties: %lld and %lld Bytes", this->mDuration, this->mSize); + + AVMetadataTag* Tag = NULL; + + while((Tag=av_metadata_get(FormatCtx->metadata,"",Tag,AV_METADATA_IGNORE_SUFFIX))){ + MESSAGE(VERBOSE_METADATA, "%s: %s", Tag->key, Tag->value); + } + + return 0; +} + +int cAudioVideoDetector::analyseVideo(AVFormatContext* FormatCtx) +{ +// // Register avformat unsigned int i; int videoStream = -1; @@ -43,24 +219,52 @@ int cAudioVideoDetector::detectVideoProperties(cUPnPResource* Resource, const ch } if(videoStream == -1){ ERROR("AVDetector: No video stream found"); - return -1; + return 1; } CodecCtx = FormatCtx->streams[videoStream]->codec; AVCodec* Codec = avcodec_find_decoder(CodecCtx->codec_id); - unsigned int width = CodecCtx->width; - unsigned int height = CodecCtx->height; - unsigned int bitrate = CodecCtx->bit_rate; + this->mWidth = CodecCtx->width; + this->mHeight = CodecCtx->height; + this->mBitrate = CodecCtx->bit_rate; + this->mSampleFrequency = CodecCtx->sample_rate; + this->mBitsPerSample = CodecCtx->bits_per_raw_sample; + + // TODO: what's the color depth of the stream + const char* codecName = (Codec)?Codec->name:"unknown"; - MESSAGE(VERBOSE_METADATA, "AVDetector: %s-stream %dx%d at %d bit/s", codecName, width, height, bitrate); + MESSAGE(VERBOSE_METADATA, "AVDetector: %s-stream %dx%d at %d bit/s", codecName, this->mWidth, this->mHeight, this->mBitrate); + return 0; +} - Resource->mBitrate = bitrate; - Resource->mSampleFrequency = CodecCtx->sample_rate; - Resource->mResolution = cString::sprintf("%dx%d", width, height); - +int cAudioVideoDetector::analyseAudio(AVFormatContext* FormatCtx){ + + unsigned int i; int audioStream = -1; + + AVCodecContext *CodecCtx = NULL; + for(i = 0; i < FormatCtx->nb_streams; i++){ + if(FormatCtx->streams[i]->codec->codec_type == CODEC_TYPE_AUDIO){ + audioStream = i; + break; + } + } + if(audioStream == -1){ + ERROR("AVDetector: No audio stream found"); + return 1; + } + + CodecCtx = FormatCtx->streams[audioStream]->codec; + AVCodec* Codec = avcodec_find_decoder(CodecCtx->codec_id); + + this->mNrAudioChannels = CodecCtx->channels; + + const char* codecName = (Codec)?Codec->name:"unknown"; + + MESSAGE(VERBOSE_METADATA, "AVDetector: %s-stream at %d bit/s", codecName, CodecCtx->bit_rate); return 0; } + diff --git a/misc/avdetector.h b/misc/avdetector.h index b043c59..33b3298 100644 --- a/misc/avdetector.h +++ b/misc/avdetector.h @@ -9,6 +9,13 @@ #define _AVDETECTOR_H #include "../database/object.h" +#include <vdr/recording.h> +#include <vdr/channels.h> + +extern "C" { +#include <libavcodec/avcodec.h> +#include <libavformat/avformat.h> +} /** * The audio/video detector @@ -18,23 +25,80 @@ * required for determination of a suitable DLNA profile. */ class cAudioVideoDetector { -public: - cAudioVideoDetector(){}; - virtual ~cAudioVideoDetector(){}; +private: + void init(); + void uninit(); + int detectChannelProperties(); + int detectFileProperties(); + int detectRecordingProperties(); /** * Detect video properties * - * This detects video properties of a video stream and stores them in the - * Resource object. + * This detects video properties of a video stream * - * @param Resource the resource, where to save the data - * @param Filename the file, which shall be read * @return returns * - \bc 0, if the detection was successful * - \bc <0, otherwise */ - int detectVideoProperties(cUPnPResource* Resource, const char* Filename); -private: + int analyseVideo(AVFormatContext* FormatCtx); + /** + * Detect audio properties + * + * This detects audio properties of a video or audio stream + * + * @return returns + * - \bc 0, if the detection was successful + * - \bc <0, otherwise + */ + int analyseAudio(AVFormatContext* FormatCtx); + int analyseFormat(AVFormatContext* FormatCtx); + int detectDLNAProfile(); + UPNP_RESOURCE_TYPES mResourceType; + union { + const cChannel* Channel; + const cRecording* Recording; + const char* Filename; + } mResource; + int mWidth; + int mHeight; + int mBitrate; + int mBitsPerSample; + int mColorDepth; + off64_t mDuration; + off64_t mSize; + int mSampleFrequency; + int mNrAudioChannels; + DLNAProfile* mDLNAProfile; +public: + cAudioVideoDetector(const char* Filename); + cAudioVideoDetector(const cChannel* Channel); + cAudioVideoDetector(const cRecording* Recording); + virtual ~cAudioVideoDetector(); + /** + * Detect resource properties of the file + * + * This detects the resource properties of a file. If the returned value + * is 0, no erros occured while detection and the properties are properly + * set. Otherwise, in case of an error, the properties may have + * unpredictable values. + * + * @return returns + * - \bc 0, if the detection was successful + * - \bc <0, otherwise + */ + int detectProperties(); + DLNAProfile* getDLNAProfile() const { return this->mDLNAProfile; } + const char* getContentType() const { return (this->mDLNAProfile) ? this->mDLNAProfile->mime : NULL; } + const char* getProtocolInfo() const; + int getWidth() const { return this->mWidth; } + int getHeight() const { return this->mHeight; } + int getBitrate() const { return this->mBitrate; } + int getBitsPerSample() const { return this->mBitsPerSample; } + int getSampleFrequency() const { return this->mSampleFrequency; } + int getNumberOfAudioChannels() const { return this->mNrAudioChannels; } + int getColorDepth() const { return this->mColorDepth; } + off64_t getFileSize() const { return this->mSize; } + off64_t getDuration() const { return this->mDuration; } }; #endif /* _AVDETECTOR_H */ diff --git a/misc/util.cpp b/misc/util.cpp index 8cfc524..d93d027 100644 --- a/misc/util.cpp +++ b/misc/util.cpp @@ -16,6 +16,50 @@ #include <upnp/ixml.h> #include <arpa/inet.h> #include <iosfwd> +#include <time.h> + +#define DURATION_MAX_STRING_LENGTH 13 // DLNA: 1-5 DIGIT hours : + // 2 DIGIT minutes : + // 2 DIGIT seconds . + // 3 DIGIT fraction + +char* duration(off64_t duration, unsigned int timeBase){ + if (timeBase == 0) timeBase = 1; + + int seconds, minutes, hours, fraction; + + seconds = duration / timeBase; + fraction = duration % timeBase; + minutes = seconds / 60; + seconds %= 60; + hours = minutes / 60; + minutes %= 60; + + char* output = new char[DURATION_MAX_STRING_LENGTH]; + + if(!snprintf( + output, + DURATION_MAX_STRING_LENGTH, + UPNP_DURATION_FORMAT_STRING, + hours, minutes, seconds) + ){ + delete output; + return NULL; + } + else { + if(timeBase > 1 && + !snprintf( + output, + DURATION_MAX_STRING_LENGTH, + "%s" UPNP_DURATION_FRAME_FORMAT, + output, fraction) + ){ + delete output; + return NULL; + } + else return output; + } +} char* substr(const char* str, unsigned int offset, unsigned int length){ if(offset > strlen(str)) return NULL; diff --git a/misc/util.h b/misc/util.h index 6ff2a54..e5bc061 100644 --- a/misc/util.h +++ b/misc/util.h @@ -105,6 +105,8 @@ int ixmlAddProperty(IN IXML_Document* document, IN IXML_Element* node, const cha * @param length the length of the new string */ char* substr(const char* str, unsigned int offset, unsigned int length); + +char* duration(off64_t duration, unsigned int timeBase = 1); #if 0 { #endif diff --git a/receiver/recplayer.cpp b/receiver/recplayer.cpp index a9ea85b..954c9c8 100644 --- a/receiver/recplayer.cpp +++ b/receiver/recplayer.cpp @@ -102,7 +102,7 @@ int cRecordingPlayer::seek(off_t offset, int origin){ while(this->mOffset > (fileEndOffset = this->mOffsets[this->mIndex])){ // If its not possible to switch to next file, there was an error if(!this->NextFile()){ - ERROR("Offset %ld not in the range of a file!", offset); + ERROR("Offset %lld not in the range of a file!", offset); return -1; } } @@ -133,7 +133,7 @@ void cRecordingPlayer::Scan(){ } fseek(File, 0, SEEK_END); off_t offset = ftell(File); - MESSAGE(VERBOSE_RECORDS, "File %d has its last offset at %ld", i, offset); + MESSAGE(VERBOSE_RECORDS, "File %d has its last offset at %lld", i, offset); this->mOffsets[i+1] = this->mOffsets[i] + offset; this->mTotalLenght = this->mOffsets[i+1]; i++; diff --git a/upnpcomponents/dlna.cpp b/upnpcomponents/dlna.cpp index 27f84f2..c8bb8a9 100644 --- a/upnpcomponents/dlna.cpp +++ b/upnpcomponents/dlna.cpp @@ -69,32 +69,6 @@ const char* cDlna::getProtocolInfo(DLNAProfile *Prof){ return NULL; } -DLNAProfile* cDlna::getProfileOfChannel(cChannel* Channel){ - if(Channel == NULL) return NULL; - // Switching the video types of the DVB-Stream - switch(Channel->Vtype()){ - case 0x02: - // MPEG2 Video - return &DLNA_PROFILE_MPEG_TS_SD_EU_ISO; - case 0x1B: - return &DLNA_PROFILE_AVC_TS_HD_EU_ISO; - default: - ERROR("Unknown video type %d for channel %s!", Channel->Vtype(), Channel->Name()); - return NULL; - } -} - -DLNAProfile* cDlna::getProfileOfRecording(cRecording* Recording){ - // Get the data of the first file of the recording - cString File = cString::sprintf(VDR_RECORDFILE_PATTERN_TS, Recording->FileName(), 1); - return this->getProfileOfFile(File); -} - -DLNAProfile* cDlna::getProfileOfFile(cString){ - WARNING("Not yet supported"); - return NULL; -} - const char* cDlna::getRegisteredProtocolInfoString(cRegisteredProfile *Profile){ cString DLNA4thField = NULL; DLNA4thField = cString::sprintf("DLNA.ORG_PN=%s", Profile->Profile->ID); diff --git a/upnpcomponents/dlna.h b/upnpcomponents/dlna.h index 80ac328..44ed3c4 100644 --- a/upnpcomponents/dlna.h +++ b/upnpcomponents/dlna.h @@ -105,39 +105,6 @@ public: const char* getProtocolInfo( DLNAProfile *Prof ///< the Profile of which the protocol info shall be returned ); - /** - * Profile of a channel - * - * Returns the DLNA profile of a VDR channel. It checks the video type to determine - * which profile will match. - * - * @return the matching DLNA profile - */ - DLNAProfile* getProfileOfChannel( - cChannel* Channel ///< the channel of which the profile should created from - ); - /** - * Profile of a recording - * - * Returns the DLNA profile of a VDR recording. It checks the video file to determine - * which profile will match. - * - * @return the matching DLNA profile - */ - DLNAProfile* getProfileOfRecording( - cRecording* Recording ///< the recording of which the profile should be created from - ); - /** - * Profile of a file - * - * Returns the DLNA profile of a file. It checks the content of the file with \em ffmpeg to - * determine which profile will match. - * - * @return the matching DLNA profile - */ - DLNAProfile* getProfileOfFile( - cString File ///< the file of which the profile should be created from - ); private: const char* getRegisteredProtocolInfoString(cRegisteredProfile *Profile); cDlna(); diff --git a/upnpcomponents/upnpwebserver.cpp b/upnpcomponents/upnpwebserver.cpp index 892f5b1..b192cd9 100644 --- a/upnpcomponents/upnpwebserver.cpp +++ b/upnpcomponents/upnpwebserver.cpp @@ -259,8 +259,8 @@ UpnpWebFileHandle cUPnPWebServer::open(const char* filename, UpnpOpenFileMode mo case UPNP_RESOURCE_CHANNEL: { char* ChannelID = strtok(strdup(Resource->getResource()),":"); - int AudioID = atoi(strtok(NULL,":")); - MESSAGE(VERBOSE_LIVE_TV, "Try to create Receiver for Channel %s with Audio ID %d", ChannelID, AudioID); + int StreamID = atoi(strtok(NULL,":")); + MESSAGE(VERBOSE_LIVE_TV, "Try to create Receiver for Channel %s with Stream ID %d", ChannelID, StreamID); cChannel* Channel = Channels.GetByChannelID(tChannelID::FromString(ChannelID)); if(!Channel){ ERROR("No such channel with ID %s", ChannelID); |