Ticket #10020: allchanges.diff

File allchanges.diff, 46.3 KB (added by Joe Bryant <tenminjoe@…>, 8 years ago)

Changes to add audio transcoding support to UPnP AND make UPnP compatible with Xbox 360

  • new file mythtv/libs/libmythupnp/audioConverterL16.cpp

    diff --git a/mythtv/libs/libmythupnp/audioConverterL16.cpp b/mythtv/libs/libmythupnp/audioConverterL16.cpp
    new file mode 100644
    index 0000000..0fa046a
    - +  
     1#include "audioConverterL16.h"
     2
     3extern "C" {
     4
     5#include "../../external/FFmpeg/libavdevice/avdevice.h"
     6#include "../../external/FFmpeg/libavutil/samplefmt.h"
     7
     8// MemoryUrlProtocol is necessary to make FFMPEG libraries output
     9// to a memory buffer rather than to a file
     10
     11#define MEMORY_URL_PROTOCOL_NAME   "memoryUrl"
     12#define MEMORY_URL_PROTOCOL_PREFIX "memoryUrl:"
     13
     14typedef struct {
     15    char* buffer;
     16    int   used;
     17} MEMORY_URL_BUF;
     18
     19static int memoryUrl_open(URLContext *urlContext, const char *dummy, int flags)
     20{
     21    MEMORY_URL_BUF* urlContextData =
     22        (MEMORY_URL_BUF*)av_malloc(sizeof(MEMORY_URL_BUF));
     23    urlContextData->buffer  = (char*)av_malloc(AVCODEC_MAX_AUDIO_FRAME_SIZE);
     24    urlContextData->used    = 0;
     25    urlContext->priv_data   = urlContextData;
     26    urlContext->is_streamed = 1;
     27    return 0;
     28}
     29
     30static int memoryUrl_write(URLContext *h, const unsigned char *buffer, int size)
     31{
     32    MEMORY_URL_BUF *audioData    = (MEMORY_URL_BUF*)h->priv_data;
     33    int             bytesToWrite = AVCODEC_MAX_AUDIO_FRAME_SIZE - audioData->used;
     34    if( size < bytesToWrite )
     35    {
     36        bytesToWrite = size;
     37    }
     38    if( bytesToWrite < 0 )
     39    {
     40        return -1;
     41    }
     42    memcpy( audioData->buffer + audioData->used, buffer, bytesToWrite );
     43    audioData->used += size;
     44    return bytesToWrite;
     45}
     46
     47static int memoryUrl_read(URLContext *h, unsigned char *buffer, int size)
     48{
     49    MEMORY_URL_BUF* audioData = (MEMORY_URL_BUF*) h->priv_data;
     50    int             bytesToReturn;
     51   
     52    if (size < audioData->used)
     53    {
     54        bytesToReturn = size;
     55    }
     56    else
     57    {
     58        bytesToReturn = audioData->used;
     59    }
     60   
     61    // Return the bytes
     62    memcpy(buffer,audioData->buffer,bytesToReturn);
     63   
     64    // Move remaining data to the front of the buffer
     65    if(bytesToReturn < audioData->used) {
     66        memmove(
     67            audioData->buffer,
     68            audioData->buffer + bytesToReturn,
     69            audioData->used - bytesToReturn);
     70    }
     71   
     72    audioData->used -= bytesToReturn;
     73    return bytesToReturn;
     74}
     75
     76static int memoryUrl_close(URLContext* h)
     77{
     78    MEMORY_URL_BUF* buffer = (MEMORY_URL_BUF*)h->priv_data;
     79    av_free(buffer->buffer);
     80    av_free(buffer);
     81    return 0;
     82}
     83
     84static URLProtocol memoryUrl_protocol = {
     85    MEMORY_URL_PROTOCOL_NAME,
     86    memoryUrl_open,
     87    memoryUrl_read,
     88    memoryUrl_write,
     89    0,
     90    memoryUrl_close
     91};
     92
     93}
     94
     95bool                 AudioConverterL16::initialised         = false;
     96const AVSampleFormat AudioConverterL16::kTargetSampleFormat = AV_SAMPLE_FMT_S16;
     97const int            AudioConverterL16::kTargetSampleRate   = 44100;
     98const int            AudioConverterL16::kTargetChannels     = 2;
     99const char          *AudioConverterL16::kTargetFormatName   = "s16be";
     100
     101AudioConverterL16::AudioConverterL16()
     102{
     103    if(!initialised)
     104    {
     105        /* must be called before using avcodec lib */
     106        avcodec_init();
     107        av_register_protocol2(&memoryUrl_protocol,sizeof(memoryUrl_protocol));
     108        avcodec_register_all();
     109        avdevice_register_all() ;
     110        av_register_all();
     111        initialised = true;
     112    }
     113   
     114    m_audioBuffer         = NULL;
     115    m_writeBuffer         = NULL;
     116    m_inputFormatContext  = NULL;
     117    m_resampleBuffer      = NULL;
     118    m_resampleContext     = NULL;
     119    m_outputFormatContext = NULL;
     120    m_openedFile          = false;
     121   
     122}
     123
     124AudioConverterL16::~AudioConverterL16()
     125{
     126   
     127    av_freep( &m_writeBuffer );
     128    av_freep( &m_audioBuffer );
     129    av_freep( &m_resampleBuffer );
     130
     131    if( m_inputFormatContext != NULL )
     132    {
     133        avformat_free_context( m_inputFormatContext );
     134    }
     135   
     136    if ( m_outputFormatContext != NULL )
     137    {
     138        if( m_outputFormatContext->pb != NULL )
     139        {
     140            url_fclose( m_outputFormatContext->pb );
     141        }
     142        avformat_free_context( m_outputFormatContext );
     143    }
     144   
     145    if( m_resampleContext != NULL )
     146    {
     147        audio_resample_close( m_resampleContext );
     148    }
     149   
     150}
     151
     152int AudioConverterL16::Open(QString inputfile)
     153{
     154   
     155    if ( m_openedFile )
     156    {
     157        // Can only open the file once - to open a new file,
     158        // create a new AudioConverterL16 object
     159        return AVERROR_NOTSUPP;
     160    }
     161   
     162    AVCodec *inputCodec;
     163    m_writeBuffer = (uint8_t*) av_malloc(AVCODEC_MAX_AUDIO_FRAME_SIZE);
     164    if( m_writeBuffer == NULL )
     165    {
     166        return AVERROR_NOMEM;
     167    }
     168
     169    const char *pseudoFilename = MEMORY_URL_PROTOCOL_PREFIX;
     170   
     171    // Open input file
     172    int error = av_open_input_file(
     173        &m_inputFormatContext,
     174        inputfile.toLocal8Bit().data(),
     175        NULL,
     176        0,
     177        NULL);
     178   
     179    if( error != 0 )
     180    {
     181        return error; // Couldn't open file
     182    }
     183   
     184    // Retrieve stream information
     185    error = av_find_stream_info(m_inputFormatContext);
     186    if( error < 0 )
     187    {
     188        return error; // Couldn't find stream information
     189    }
     190
     191    // Find the first audio stream
     192    bool foundStream = false;
     193    for(
     194        m_inputStreamIndex = 0;
     195        m_inputStreamIndex < m_inputFormatContext->nb_streams;
     196        m_inputStreamIndex++ )
     197    {
     198        if( m_inputFormatContext->streams[m_inputStreamIndex]->codec->codec_type
     199            == CODEC_TYPE_AUDIO )
     200        {
     201            foundStream = true;
     202            break;
     203        }
     204    }
     205    if (!foundStream)
     206    {
     207        return AVERROR_NOFMT;
     208    }
     209   
     210    // Get a pointer to the codec context for the audio stream
     211    m_inputCodecContext=m_inputFormatContext->streams[m_inputStreamIndex]->codec;
     212   
     213    // Find the decoder for the audio stream
     214    inputCodec = avcodec_find_decoder(m_inputCodecContext->codec_id);
     215    if( inputCodec == NULL )
     216    {
     217        return AVERROR_NOFMT;
     218    }
     219   
     220    // Inform the codec that we can handle truncated bitstreams -- i.e.,
     221    // bitstreams where frame boundaries can fall in the middle of packets
     222    if( inputCodec->capabilities & CODEC_CAP_TRUNCATED )
     223    {
     224        m_inputCodecContext->flags |= CODEC_FLAG_TRUNCATED;
     225    }
     226
     227    // Open codec
     228    if( avcodec_open(m_inputCodecContext, inputCodec) < 0 )
     229    {
     230        return AVERROR_NOFMT;
     231    }
     232   
     233    m_audioBuffer = (int16_t*) av_malloc(AVCODEC_MAX_AUDIO_FRAME_SIZE);
     234    if( m_audioBuffer == NULL )
     235    {
     236        return AVERROR_NOMEM;
     237    }
     238   
     239    // Resample if input rate not allowed or wrong number of channels
     240    if(
     241           m_inputCodecContext->sample_fmt  != kTargetSampleFormat
     242        || m_inputCodecContext->channels    != kTargetChannels
     243        || m_inputCodecContext->sample_rate != kTargetSampleRate
     244    )
     245    {
     246        m_resampling = true;
     247        m_resampleContext = av_audio_resample_init(
     248            kTargetChannels,
     249            m_inputCodecContext->channels,
     250            kTargetSampleRate,
     251            m_inputCodecContext->sample_rate,
     252            kTargetSampleFormat,
     253            m_inputCodecContext->sample_fmt,
     254            16, 10, 0, 0.8); // These values cribbed from libavcodec/resample.c
     255       
     256        if ( m_resampleContext == NULL )
     257        {
     258            return AVERROR_UNKNOWN;
     259        }
     260           
     261        m_resampleBuffer = (short*) av_malloc(
     262            (AVCODEC_MAX_AUDIO_FRAME_SIZE / GetBytesPerSampleOut()) * 4 + 16);
     263       
     264        if ( m_resampleBuffer == NULL )
     265        {
     266            return AVERROR_NOMEM;
     267        }
     268    }
     269    else
     270    {
     271        m_resampling = false;
     272    }
     273   
     274    AVOutputFormat* outputFormat = av_guess_format(
     275        kTargetFormatName,
     276        NULL,
     277        NULL );
     278   
     279    if ( outputFormat == NULL )
     280    {
     281        return AVERROR_NOFMT;
     282    }
     283   
     284    m_outputFormatContext = avformat_alloc_context();
     285    m_outputFormatContext->oformat = outputFormat;
     286   
     287    m_outputAudioStream = av_new_stream( m_outputFormatContext, 1 );
     288    if ( !m_outputAudioStream ) {
     289        return AVERROR_UNKNOWN;
     290    }
     291
     292    m_outputAudioStream->codec->codec_id    = outputFormat->audio_codec;
     293    m_outputAudioStream->codec->codec_type  = AVMEDIA_TYPE_AUDIO;
     294    m_outputAudioStream->codec->sample_fmt  = kTargetSampleFormat;
     295    m_outputAudioStream->codec->sample_rate = kTargetSampleRate;
     296    m_outputAudioStream->codec->channels    = kTargetChannels;
     297
     298    // some formats want stream headers to be separate
     299    if( m_outputFormatContext->oformat->flags & AVFMT_GLOBALHEADER )
     300        m_outputAudioStream->codec->flags |= CODEC_FLAG_GLOBAL_HEADER;
     301
     302    if ( av_set_parameters(m_outputFormatContext, NULL) < 0 )
     303    {
     304        return AVERROR_UNKNOWN;
     305    }
     306
     307    AVCodec *codec = avcodec_find_encoder(
     308        m_outputAudioStream->codec->codec_id );
     309   
     310    if ( codec == NULL )
     311    {
     312        return AVERROR_NOFMT;
     313    }
     314
     315    /* open it */
     316    if ( avcodec_open( m_outputAudioStream->codec, codec ) < 0 )
     317    {
     318        return AVERROR_NOFMT;
     319    }
     320
     321    error = url_fopen( &m_outputFormatContext->pb, pseudoFilename, URL_WRONLY );
     322    if ( error < 0 )
     323    {
     324        return error;
     325    }
     326   
     327    m_urlContext  = url_fileno( m_outputFormatContext->pb );
     328    if( m_urlContext == 0 )
     329    {
     330        return AVERROR_UNKNOWN;
     331    }
     332   
     333    m_doneHeader  = false;
     334    m_doneTrailer = false;
     335    m_openedFile  = true;
     336   
     337    m_bytesPerSampleIn = av_get_bits_per_sample_fmt(
     338        m_inputCodecContext->sample_fmt ) / 8;
     339       
     340    if ( m_bytesPerSampleIn == 0 )
     341    {
     342        return AVERROR_NOFMT;
     343    }
     344   
     345    if ( GetBytesPerSampleOut() == 0 )
     346    {
     347        return AVERROR_NOFMT;
     348    }
     349   
     350    m_outputLength =
     351        (m_inputFormatContext->duration *
     352        kTargetSampleRate *
     353        kTargetChannels *
     354        GetBytesPerSampleOut( ) ) /
     355        AV_TIME_BASE;
     356   
     357    return 0;
     358
     359}
     360
     361const int64_t AudioConverterL16::GetOutputLength( )
     362{
     363    return m_outputLength;
     364}
     365
     366const int AudioConverterL16::GetOutputRate( )
     367{
     368    return kTargetSampleRate;
     369}
     370
     371int AudioConverterL16::GetChunk( unsigned char* buffer )
     372{
     373    int buffer_size;
     374    int decompressedBytes;
     375    int error;
     376    AVPacket packet;
     377
     378    if( !m_openedFile )
     379    {
     380        return AVERROR_NOTSUPP;
     381    }
     382   
     383    if( m_doneTrailer )
     384    {
     385        return 0;
     386    }
     387   
     388    if( !m_doneHeader )
     389    {
     390        error = av_write_header( m_outputFormatContext );
     391        if ( error != 0 )
     392        {
     393            return error;
     394        }
     395        m_doneHeader = true;
     396    }
     397   
     398    bool wroteAnything = false;
     399   
     400    while ( av_read_frame( m_inputFormatContext, &packet ) >=0 )
     401    {
     402        // Is this a packet from the audio stream?
     403        if( (unsigned int) packet.stream_index==m_inputStreamIndex )
     404        {
     405            buffer_size = AVCODEC_MAX_AUDIO_FRAME_SIZE;
     406            decompressedBytes = avcodec_decode_audio3(
     407                m_inputCodecContext,
     408                m_audioBuffer,
     409                &buffer_size,
     410                &packet );
     411           
     412            if( decompressedBytes < 0 )
     413            {
     414                av_free_packet( &packet );
     415                return AVERROR_INVALIDDATA;
     416            }
     417            else if ( decompressedBytes < packet.size )
     418            {
     419                av_free_packet( &packet );
     420                return AVERROR_INVALIDDATA;
     421            }
     422            else if ( decompressedBytes > 0 && buffer_size > 0 )
     423            {
     424                if( m_resampling )
     425                {
     426                    int outputSamples = audio_resample(
     427                        m_resampleContext,
     428                        m_resampleBuffer,
     429                        m_audioBuffer,
     430                        buffer_size /
     431                            ( m_bytesPerSampleIn * m_inputCodecContext->channels )
     432                    );
     433                   
     434                    if (outputSamples == 0)
     435                    {
     436                        av_free_packet( &packet );
     437                        return AVERROR_UNKNOWN;
     438                    }
     439                    error = WriteAudioFrame(
     440                        m_resampleBuffer,
     441                        outputSamples * GetBytesPerSampleOut() * kTargetChannels
     442                    );
     443                }
     444                else
     445                {
     446                    error = WriteAudioFrame( m_audioBuffer, buffer_size );
     447                }
     448                if ( error != 0 )
     449                {
     450                    av_free_packet( &packet );
     451                    return error;
     452                }
     453                wroteAnything = true;
     454                break;
     455            }
     456        }
     457        av_free_packet( &packet );
     458    }
     459   
     460    // If nothing was written, then we've reached the end
     461    if( !wroteAnything )
     462    {
     463        error = av_write_trailer( m_outputFormatContext );
     464        if ( error != 0 )
     465        {
     466            return error;
     467        }
     468        m_doneTrailer = true;
     469    }
     470   
     471    put_flush_packet( m_outputFormatContext->pb );
     472   
     473    return memoryUrl_read( m_urlContext, buffer, AVCODEC_MAX_AUDIO_FRAME_SIZE );
     474
     475}
     476
     477int AudioConverterL16::WriteAudioFrame( int16_t* samples, int bufferSize )
     478{
     479    AVCodecContext *codecContext;
     480    AVPacket packet;
     481    av_init_packet(&packet);
     482    codecContext = m_outputAudioStream->codec;
     483
     484    packet.size = avcodec_encode_audio( codecContext, m_writeBuffer, bufferSize, samples );
     485    if ( packet.size < 0 )
     486    {
     487        return AVERROR_UNKNOWN;
     488    }
     489
     490    if ( codecContext->coded_frame && codecContext->coded_frame->pts != (int64_t) AV_NOPTS_VALUE )
     491    {
     492        packet.pts= av_rescale_q(
     493            codecContext->coded_frame->pts,
     494            codecContext->time_base,
     495            m_outputAudioStream->time_base );
     496    }
     497   
     498    packet.flags |= AV_PKT_FLAG_KEY;
     499    packet.stream_index= m_outputAudioStream->index;
     500    packet.data= m_writeBuffer;
     501
     502    /* write the compressed frame in the media file */
     503    if ( av_write_frame (m_outputFormatContext, &packet ) < 0 )
     504    {
     505        return AVERROR_UNKNOWN;
     506    }
     507   
     508    return 0;
     509}
     510
     511const int AudioConverterL16::GetOutputChannels( )
     512{
     513    return kTargetChannels;
     514}
     515
     516const int AudioConverterL16::GetBitsPerSample( )
     517{
     518    return 8 * GetBytesPerSampleOut();
     519}
     520
     521const int AudioConverterL16::GetBytesPerSampleOut( )
     522{
     523    static int bytesPerSampleOut = 0;
     524    if (bytesPerSampleOut == 0)
     525    {
     526        bytesPerSampleOut = av_get_bits_per_sample_fmt(kTargetSampleFormat) / 8;
     527    }
     528    return bytesPerSampleOut;
     529}
     530
  • new file mythtv/libs/libmythupnp/audioConverterL16.h

    diff --git a/mythtv/libs/libmythupnp/audioConverterL16.h b/mythtv/libs/libmythupnp/audioConverterL16.h
    new file mode 100644
    index 0000000..2f6ea6e
    - +  
     1#ifndef AUDIOCONVERTERL16_H
     2#define AUDIOCONVERTERL16_H
     3
     4#include <QString>
     5
     6extern "C" {
     7
     8#include "../../external/FFmpeg/libavformat/avformat.h"
     9#include "../../external/FFmpeg/libavutil/samplefmt.h"
     10
     11}
     12
     13class AudioConverterL16
     14{
     15
     16public:
     17
     18    AudioConverterL16();
     19    virtual ~AudioConverterL16();
     20
     21    // Opens the indicated file for conversion
     22    // Returns 0 on success, else negative value
     23    int              Open                 ( QString inputfile );
     24   
     25    // Outputs a chunk of converted bytes.
     26    // Buffer size must be at least AVCODEC_MAX_AUDIO_FRAME_SIZE bytes
     27    // Returns number of bytes output into buffer.
     28    // If no more bytes to return, returns 0.
     29    // If error, returns negative number.
     30    int              GetChunk             ( unsigned char *buffer );
     31   
     32    // Length in bytes of expected output
     33    const int64_t    GetOutputLength      ( );
     34   
     35    // The sample rate that will be output
     36    static const int GetOutputRate        ( );
     37   
     38    // Output channels
     39    static const int GetOutputChannels    ( );
     40
     41    // Output bits per sample
     42    static const int GetBitsPerSample     ( );
     43   
     44private:
     45    int              WriteAudioFrame      ( int16_t *samples, int bufferSize );
     46    static const int GetBytesPerSampleOut ( );
     47
     48    bool                        m_resampling;
     49    bool                        m_openedFile;
     50    bool                        m_doneHeader;
     51    bool                        m_doneTrailer;
     52    URLContext                 *m_urlContext;
     53    AVFormatContext            *m_outputFormatContext;
     54    AVFormatContext            *m_inputFormatContext;
     55    unsigned int                m_inputStreamIndex;
     56    AVCodecContext             *m_inputCodecContext;
     57    int16_t                    *m_audioBuffer;
     58    ReSampleContext            *m_resampleContext;
     59    short                      *m_resampleBuffer;
     60    AVStream                   *m_outputAudioStream;
     61    uint8_t                    *m_writeBuffer;
     62    int64_t                     m_outputLength;
     63    int                         m_bytesPerSampleIn;
     64   
     65    static bool                 initialised;
     66    static const AVSampleFormat kTargetSampleFormat;
     67    static const int            kTargetSampleRate;
     68    static const int            kTargetChannels;
     69    static const char          *kTargetFormatName;
     70
     71};
     72
     73#endif // AUDIOCONVERTERL16_H
  • mythtv/libs/libmythupnp/httprequest.cpp

    diff --git a/mythtv/libs/libmythupnp/httprequest.cpp b/mythtv/libs/libmythupnp/httprequest.cpp
    index 75d6366..dcfb320 100644
    a b  
    5454#include "serializers/xmlSerializer.h"
    5555#include "serializers/soapSerializer.h"
    5656#include "serializers/jsonSerializer.h"
     57#include "audioConverterL16.h"
    5758
    5859#ifndef O_LARGEFILE
    5960#define O_LARGEFILE 0
    long HTTPRequest::SendResponse( void ) 
    282283    return( nBytes );
    283284}
    284285
     286long HTTPRequest::SendL16ResponseFile( QString sFileName )
     287{
     288    long nBytes = 0;
     289   
     290    LOG(VB_UPNP, LOG_INFO, QString("SendL16ResponseFile ( %1 )").arg(sFileName));
     291
     292    m_eResponseType     = ResponseTypeOther;
     293    m_sResponseTypeText = "text/plain";
     294
     295    // ----------------------------------------------------------------------
     296    // Make it so the header is sent with the data
     297    // ----------------------------------------------------------------------
     298
     299#ifdef USE_SETSOCKOPT
     300    // Never send out partially complete segments
     301    setsockopt( getSocketHandle(), SOL_TCP, TCP_CORK, &g_on, sizeof( g_on ));
     302#endif
     303
     304    QFile tmpFile( sFileName );
     305    if (tmpFile.exists( ) && tmpFile.open( QIODevice::ReadOnly ))
     306    {
     307        m_sResponseTypeText = QString("audio/L16;rate=%1;channels=%2").arg(AudioConverterL16::GetOutputRate()).arg(AudioConverterL16::GetOutputChannels());
     308        m_nResponseStatus = 200;
     309        m_mapRespHeaders[ "User-Agent"    ] = "redsonic";
     310    }
     311    else
     312    {
     313        LOG(VB_UPNP, LOG_INFO,
     314            QString("HTTPRequest::SendL16ResponseFile(%1) - cannot find file!")
     315                .arg(sFileName));
     316        m_nResponseStatus = 404;
     317    }
     318       
     319    QString sRange = GetHeaderValue( "range", "" );
     320
     321    // ----------------------------------------------------------------------
     322    // Write out Header.
     323    // ----------------------------------------------------------------------
     324    AudioConverterL16* converter = new AudioConverterL16();
     325    int error = converter->Open(tmpFile.fileName());
     326    if (error < 0)
     327    {
     328        LOG(VB_UPNP, LOG_INFO,
     329            QString("HTTPRequest::SendL16ResponseFile(%1) - cannot convert file! AVError %2")
     330                .arg(sFileName)
     331                .arg(error));
     332        m_nResponseStatus = 404;
     333    }
     334   
     335    QString rHeader = BuildHeader( converter->GetOutputLength() );
     336    QByteArray sHeader = rHeader.toUtf8();
     337    nBytes = WriteBlockDirect( sHeader.constData(), sHeader.length() );
     338
     339    // ----------------------------------------------------------------------
     340    // Write out File.
     341    // ----------------------------------------------------------------------
     342
     343    unsigned char *buffer         = new unsigned char[AVCODEC_MAX_AUDIO_FRAME_SIZE];
     344    int            bytesConverted = 0;
     345    qlonglong      bytesWritten   = 0;
     346    bool           converterError = false;
     347    do
     348    {
     349        bytesConverted = converter->GetChunk(buffer);
     350        if( bytesConverted < 0 )
     351        {
     352            converterError = true;
     353            break;
     354        }
     355        bytesWritten = 0;
     356        while(bytesWritten < bytesConverted)
     357        {
     358            qlonglong bytesWrittenThisTime = WriteBlockDirect((const char*)buffer + bytesWritten, bytesConverted - bytesWritten);
     359            if (bytesWrittenThisTime > 0)
     360            {
     361                bytesWritten += bytesWrittenThisTime;
     362            }
     363            else
     364            {
     365                break;
     366            }
     367        }
     368    } while (bytesWritten == bytesConverted && bytesConverted > 0);
     369   
     370    if(converterError)
     371    {
     372         LOG(VB_UPNP, LOG_INFO,
     373            QString("SendL16ResponseFile( %1 ) - failed during file conversion! AVError %2" )
     374                .arg(sFileName)
     375                .arg(error));
     376         nBytes = -1;
     377    }
     378   
     379    if( bytesWritten != bytesConverted)
     380    {
     381         LOG(VB_UPNP, LOG_INFO,
     382            QString("SendL16ResponseFile( %1 ) aborted by client" ).arg(sFileName));
     383    }
     384    delete(buffer);
     385    delete(converter);
     386   
     387    // ----------------------------------------------------------------------
     388    // Turn off the option so any small remaining packets will be sent
     389    // ----------------------------------------------------------------------
     390
     391#ifdef USE_SETSOCKOPT
     392    setsockopt( getSocketHandle(), SOL_TCP, TCP_CORK, &g_off, sizeof( g_off ));
     393#endif
     394
     395    // -=>TODO: Only returns header length...
     396    //          should we change to return total bytes?
     397
     398    return nBytes;
     399
     400}
     401
    285402/////////////////////////////////////////////////////////////////////////////
    286403//
    287404/////////////////////////////////////////////////////////////////////////////
    long HTTPRequest::SendResponseFile( QString sFileName ) 
    292409    long long   llSize  = 0;
    293410    long long   llStart = 0;
    294411    long long   llEnd   = 0;
     412   
     413    if( m_sMethod == "GetMusic" && m_mapParams["format"] == "L16")
     414    {
     415        return SendL16ResponseFile(sFileName);
     416    }
    295417
    296418    LOG(VB_UPNP, LOG_INFO, QString("SendResponseFile ( %1 )").arg(sFileName));
    297419
  • mythtv/libs/libmythupnp/httprequest.h

    diff --git a/mythtv/libs/libmythupnp/httprequest.h b/mythtv/libs/libmythupnp/httprequest.h
    index 06bd2b3..df3421f 100644
    a b class UPNP_PUBLIC HTTPRequest 
    193193        void            FormatFileResponse  ( const QString &sFileName );
    194194        void            FormatRawResponse   ( const QString &sXML );
    195195
    196         long            SendResponse    ( void );
    197         long            SendResponseFile( QString sFileName );
     196        long            SendResponse        ( void );
     197        long            SendResponseFile    ( QString sFileName );
     198        long            SendL16ResponseFile ( QString sFileName );
    198199
    199200        QString         GetHeaderValue  ( const QString &sKey, QString sDefault );
    200201
  • mythtv/libs/libmythupnp/libmythupnp.pro

    diff --git a/mythtv/libs/libmythupnp/libmythupnp.pro b/mythtv/libs/libmythupnp/libmythupnp.pro
    index ec48bfb..09b14be 100644
    a b HEADERS += configuration.h 
    2626HEADERS += soapclient.h mythxmlclient.h mmembuf.h upnpexp.h
    2727HEADERS += upnpserviceimpl.h
    2828HEADERS += servicehost.h wsdl.h htmlserver.h serverSideScripting.h
     29HEADERS += audioConverterL16.h
    2930
    3031HEADERS += serializers/serializer.h     serializers/xmlSerializer.h
    3132HEADERS += serializers/jsonSerializer.h serializers/soapSerializer.h
    SOURCES += configuration.cpp soapclient.cpp mythxmlclient.cpp mmembuf.cpp 
    3940SOURCES += upnpserviceimpl.cpp
    4041SOURCES += htmlserver.cpp serverSideScripting.cpp
    4142SOURCES += servicehost.cpp wsdl.cpp upnpsubscription.cpp
     43SOURCES += audioConverterL16.cpp
    4244
    4345SOURCES += serializers/serializer.cpp     serializers/xmlSerializer.cpp
    4446SOURCES += serializers/jsonSerializer.cpp
    4547
    4648INCLUDEPATH += ../libmythbase ../libmythservicecontracts ..
    4749INCLUDEPATH += ./serializers
     50INCLUDEPATH += ../../external/FFmpeg
    4851
    4952DEPENDPATH  += ../libmythbase ..
    5053LIBS      += -L../libmythbase -lmythbase-$$LIBVERSION
  • mythtv/libs/libmythupnp/upnpcds.cpp

    diff --git a/mythtv/libs/libmythupnp/upnpcds.cpp b/mythtv/libs/libmythupnp/upnpcds.cpp
    index 791d8a0..1222e9b 100644
    a b void UPnpCDS::HandleSearch( HTTPRequest *pRequest ) 
    533533    // ----------------------------------------------------------------------
    534534    // -=>TODO: Need to process all expressions in searchCriteria... for now,
    535535    //          Just focus on the "upnp:class derivedfrom" expression
     536    //          and the "upnp:class =" expression
    536537    // ----------------------------------------------------------------------
    537538
    538539    for ( QStringList::Iterator it  = request.m_sSearchList.begin();
    539540                                it != request.m_sSearchList.end();
    540541                              ++it )
    541542    {
    542         if ((*it).contains("upnp:class derivedfrom", Qt::CaseInsensitive))
     543        if ((*it).contains("upnp:class derivedfrom", Qt::CaseInsensitive) ||
     544            (*it).contains("upnp:class =", Qt::CaseInsensitive))
    543545        {
    544546            QStringList sParts = (*it).split(' ', QString::SkipEmptyParts);
    545547
    void UPnpCDS::HandleSearch( HTTPRequest *pRequest ) 
    547549            {
    548550                request.m_sSearchClass = sParts[2].trimmed();
    549551                request.m_sSearchClass.remove( '"' );
     552                request.m_sSearchClass.remove( ')' );
    550553
    551554                break;
    552555            }
    UPnpCDSExtensionResults *UPnpCDSExtension::Search( UPnpCDSRequest *pRequest ) 
    824827
    825828    UPnpCDSExtensionResults *pResults = new UPnpCDSExtensionResults();
    826829
    827     CreateItems( pRequest, pResults, 0, "", false );
     830    QString sKey = "";
     831    if (IsOurContainerPrefix(pRequest->m_sContainerID))
     832    {
     833        sKey = RemoveContainerPrefix(pRequest->m_sContainerID);
     834    }
     835    CreateItems( pRequest, pResults, 0, sKey, false );
    828836
    829837    return pResults;
    830838}
    void UPnpCDSExtension::CreateItems( UPnpCDSRequest *pRequest, 
    13261334    pResults->m_nTotalMatches = 0;
    13271335    pResults->m_nUpdateID     = 1;
    13281336
    1329     UPnpCDSRootInfo *pInfo = GetRootInfo( nNodeIdx );
     1337    UPnpCDSRootInfo *pInfo = GetRootInfo( nNodeIdx, pRequest->m_sContainerID );
    13301338
    13311339    if (pInfo == NULL)
    13321340        return;
  • mythtv/libs/libmythupnp/upnpcds.h

    diff --git a/mythtv/libs/libmythupnp/upnpcds.h b/mythtv/libs/libmythupnp/upnpcds.h
    index ed3b094..9e79655 100644
    a b class UPNP_PUBLIC UPnpCDSExtension 
    167167
    168168    protected:
    169169
     170        virtual bool IsOurContainerPrefix(QString sContainerID) { return false; };
     171        virtual QString RemoveContainerPrefix(QString sContainerID) { return sContainerID; };
    170172        QString RemoveToken ( const QString &sToken, const QString &sStr, int num );
    171173
    172174        virtual UPnpCDSExtensionResults *ProcessRoot     ( UPnpCDSRequest          *pRequest,
    class UPNP_PUBLIC UPnpCDSExtension 
    202204
    203205        // ------------------------------------------------------------------
    204206
    205         virtual UPnpCDSRootInfo *GetRootInfo   ( int nIdx) = 0;
     207        virtual UPnpCDSRootInfo *GetRootInfo   ( int nIdx, QString sContainerId = "" ) = 0;
    206208        virtual int              GetRootCount  ( )         = 0;
    207209        virtual QString          GetTableName  ( QString sColumn      ) = 0;
    208210        virtual QString          GetItemListSQL( QString sColumn = "" ) = 0;
  • mythtv/programs/mythbackend/mediaserver.cpp

    diff --git a/mythtv/programs/mythbackend/mediaserver.cpp b/mythtv/programs/mythbackend/mediaserver.cpp
    index 14891ca..9e53476 100644
    a b  
    1616#include "upnpcdstv.h"
    1717#include "upnpcdsmusic.h"
    1818#include "upnpcdsvideo.h"
     19#include "upnpcdsmusicalbum.h"
    1920
    2021#include <QScriptEngine>
    2122#include <QNetworkProxy>
    void MediaServer::Init(bool bIsMaster, bool bDisableUPnp /* = false */) 
    230231                "MediaServer::Registering UPnpCDSVideo Extension");
    231232
    232233            RegisterExtension(new UPnpCDSVideo());
     234
     235            LOG(VB_UPNP, LOG_INFO,
     236                "MediaServer::Registering UPnpCDSMusicAlbum Extension");
     237
     238            RegisterExtension(new UPnpCDSMusicAlbum());
    233239        }
    234240
    235241#if 0
  • mythtv/programs/mythbackend/mythbackend.pro

    diff --git a/mythtv/programs/mythbackend/mythbackend.pro b/mythtv/programs/mythbackend/mythbackend.pro
    index ac8742c..bcc168c 100644
    a b HEADERS += playbacksock.h scheduler.h server.h housekeeper.h backendutil.h 
    2323HEADERS += upnpcdstv.h upnpcdsmusic.h upnpcdsvideo.h mediaserver.h
    2424HEADERS += internetContent.h main_helpers.h backendcontext.h
    2525HEADERS += httpconfig.h mythsettings.h commandlineparser.h
     26HEADERS += upnpcdsmusicalbum.h audioConverterL16.h
    2627
    2728HEADERS += serviceHosts/mythServiceHost.h    serviceHosts/guideServiceHost.h
    2829HEADERS += serviceHosts/contentServiceHost.h serviceHosts/dvrServiceHost.h
    SOURCES += housekeeper.cpp backendutil.cpp 
    3738SOURCES += upnpcdstv.cpp upnpcdsmusic.cpp upnpcdsvideo.cpp mediaserver.cpp
    3839SOURCES += internetContent.cpp main_helpers.cpp backendcontext.cpp
    3940SOURCES += httpconfig.cpp mythsettings.cpp commandlineparser.cpp
     41SOURCES += upnpcdsmusicalbum.cpp audioConverterL16.cpp
    4042
    4143SOURCES += services/myth.cpp services/guide.cpp services/content.cpp
    4244SOURCES += services/dvr.cpp services/channel.cpp services/video.cpp
  • mythtv/programs/mythbackend/upnpcdsmusic.cpp

    diff --git a/mythtv/programs/mythbackend/upnpcdsmusic.cpp b/mythtv/programs/mythbackend/upnpcdsmusic.cpp
    index 57cc570..55d8ef9 100644
    a b  
    1515#include "upnpcdsmusic.h"
    1616#include "httprequest.h"
    1717#include "mythcorecontext.h"
     18#include "../../mythtv/libs/libmythupnp/audioConverterL16.h"
    1819
    1920/*
    2021   Music                            Music
    UPnpCDSRootInfo UPnpCDSMusic::g_RootNodes[] = 
    109110
    110111int UPnpCDSMusic::g_nRootCount = sizeof( g_RootNodes ) / sizeof( UPnpCDSRootInfo );
    111112
     113UPnpCDSRootInfo UPnpCDSMusic::g_Containers[] =
     114{
     115     {
     116        "musicalbum",
     117        "song.album_id",
     118        "SELECT song_id as id, "
     119        "name, "
     120        "1 as children "
     121        "FROM music_songs song "
     122        "%1 "
     123        "ORDER BY name",
     124        "WHERE album_id = :KEY"
     125     }
     126};
     127
     128int UPnpCDSMusic::g_nContainersCount = sizeof( g_Containers ) / sizeof( UPnpCDSRootInfo );
     129
    112130/////////////////////////////////////////////////////////////////////////////
    113131//
    114132/////////////////////////////////////////////////////////////////////////////
    115133
    116 UPnpCDSRootInfo *UPnpCDSMusic::GetRootInfo( int nIdx )
     134UPnpCDSRootInfo *UPnpCDSMusic::GetRootInfo( int nIdx, QString sContainerId )
    117135{
     136    for(int i=0; i<g_nContainersCount; i++)
     137    {
     138        if (sContainerId.startsWith(g_Containers[ i ].title))
     139        {
     140            return &(g_Containers[ i ]);
     141        }
     142    }
     143       
    118144    if ((nIdx >=0 ) && ( nIdx < g_nRootCount ))
    119145        return &(g_RootNodes[ nIdx ]);
    120146
    void UPnpCDSMusic::BuildItemQuery( MSqlQuery &query, const QStringMap &mapParams 
    175201//
    176202/////////////////////////////////////////////////////////////////////////////
    177203
     204bool UPnpCDSMusic::IsOurContainerPrefix(QString sContainerId)
     205{
     206    for(int i=0; i<g_nContainersCount; i++)
     207    {
     208        if (sContainerId.startsWith(g_Containers[ i ].title))
     209        {
     210            return true;
     211        }
     212    }
     213    return false;
     214}
     215
     216/////////////////////////////////////////////////////////////////////////////
     217//
     218/////////////////////////////////////////////////////////////////////////////
     219
     220QString UPnpCDSMusic::RemoveContainerPrefix(QString sContainerId)
     221{
     222    for(int i=0; i<g_nContainersCount; i++)
     223    {
     224        if (sContainerId.startsWith(g_Containers[ i ].title))
     225        {
     226            return sContainerId.right(sContainerId.length() - strlen(g_Containers[ i ].title));
     227        }
     228    }
     229    return sContainerId;
     230}
     231
     232/////////////////////////////////////////////////////////////////////////////
     233//
     234/////////////////////////////////////////////////////////////////////////////
     235
    178236bool UPnpCDSMusic::IsBrowseRequestForUs( UPnpCDSRequest *pRequest )
    179237{
    180238    // ----------------------------------------------------------------------
    bool UPnpCDSMusic::IsBrowseRequestForUs( UPnpCDSRequest *pRequest ) 
    182240    // ----------------------------------------------------------------------
    183241
    184242    // Xbox360 compatibility code.
    185 
     243    /*
    186244    if (pRequest->m_eClient == CDS_ClientXBox &&
    187245        pRequest->m_sContainerID == "7")
    188246    {
    bool UPnpCDSMusic::IsBrowseRequestForUs( UPnpCDSRequest *pRequest ) 
    193251
    194252        return true;
    195253    }
    196 
     254    */
    197255    if ((pRequest->m_sObjectId.isEmpty()) &&
    198256        (!pRequest->m_sContainerID.isEmpty()))
    199257        pRequest->m_sObjectId = pRequest->m_sContainerID;
    bool UPnpCDSMusic::IsSearchRequestForUs( UPnpCDSRequest *pRequest ) 
    215273    // ----------------------------------------------------------------------
    216274
    217275    // XBox 360 compatibility code
    218 
     276    /*
    219277    if (pRequest->m_eClient == CDS_ClientXBox &&
    220278        pRequest->m_sContainerID == "7")
    221279    {
    bool UPnpCDSMusic::IsSearchRequestForUs( UPnpCDSRequest *pRequest ) 
    227285
    228286        return true;
    229287    }
    230 
     288    */
    231289    if (pRequest->m_sContainerID == "4")
    232290    {
    233291        pRequest->m_sObjectId       = "Music";
    void UPnpCDSMusic::AddItem( const UPnpCDSRequest *pRequest, 
    308366                            .arg( sServerIp )
    309367                            .arg( sPort     );
    310368
    311     QString sURIParams = QString( "?Id=%1" )
     369    QString sURIParams = QString( "\%3FId\%3D%1" )
    312370                            .arg( nId );
    313371
    314372
    void UPnpCDSMusic::AddItem( const UPnpCDSRequest *pRequest, 
    365423    QString sURI      = QString( "%1GetMusic%2").arg( sURIBase   )
    366424                                                .arg( sURIParams );
    367425
    368     Resource *pRes = pItem->AddResource( sProtocol, sURI );
     426    // DLNA.ORG_OP=00 indicates ranges/seeking not supported
     427    // DLNA.ORG_CI=1 indicates transcoded                                               
     428    QString sTranscodedProtocol = QString( "http-get:*:audio/L16:DLNA.ORG_PN=LPCM;DLNA.ORG_OP=00;DLNA.ORG_CI=1" );
     429    QString sTranscodedURI      = sURI + "%26format=L16";
    369430
    370     nLength /= 1000;
     431    Resource *pRes           = pItem->AddResource( sProtocol, sURI );
     432    Resource *pTranscodedRes = pItem->AddResource( sTranscodedProtocol, sTranscodedURI );
    371433
    372434    QString sDur;
    373435
    374     sDur.sprintf("%02d:%02d:%02d",
    375                   (nLength / 3600) % 24,
    376                   (nLength / 60) % 60,
    377                   nLength % 60);
    378 
    379     pRes->AddAttribute( "duration"  , sDur      );
     436    sDur.sprintf("%02d:%02d:%02d.%03d",
     437                 (nLength / 3600000) % 24,
     438                 (nLength / 60000) % 60,
     439                 (nLength / 1000) % 60,
     440                  nLength % 1000
     441                );
     442
     443    pRes->AddAttribute           ( "duration",        sDur );
     444    pTranscodedRes->AddAttribute ( "duration",        sDur );
     445   
     446    pTranscodedRes->AddAttribute ( "bitrate",         QString("%1").arg(
     447        AudioConverterL16::GetBitsPerSample() *
     448        AudioConverterL16::GetOutputRate() *
     449        AudioConverterL16::GetOutputChannels()
     450    ));
     451    pTranscodedRes->AddAttribute ( "bitsPerSample",   QString("%1").arg( AudioConverterL16::GetBitsPerSample()  ) );
     452    pTranscodedRes->AddAttribute ( "sampleFrequency", QString("%1").arg( AudioConverterL16::GetOutputRate()     ) );
     453    pTranscodedRes->AddAttribute ( "nrAudioChannels", QString("%1").arg( AudioConverterL16::GetOutputChannels() ) );
     454   
    380455}
    381456
    382457// vim:ts=4:sw=4:ai:et:si:sts=4
  • mythtv/programs/mythbackend/upnpcdsmusic.h

    diff --git a/mythtv/programs/mythbackend/upnpcdsmusic.h b/mythtv/programs/mythbackend/upnpcdsmusic.h
    index 5cc28a6..815d38e 100644
    a b class UPnpCDSMusic : public UPnpCDSExtension 
    2424    private:
    2525
    2626        static UPnpCDSRootInfo g_RootNodes[];
     27        static UPnpCDSRootInfo g_Containers[];
    2728        static int             g_nRootCount;
     29        static int             g_nContainersCount;
    2830
    2931    protected:
    3032
     33        virtual bool             IsOurContainerPrefix ( QString sContainerId );
     34        virtual QString          RemoveContainerPrefix ( QString sContainerId );
    3135        virtual bool             IsBrowseRequestForUs( UPnpCDSRequest *pRequest );
    3236        virtual bool             IsSearchRequestForUs( UPnpCDSRequest *pRequest );
    3337
    34         virtual UPnpCDSRootInfo *GetRootInfo   (int nIdx);
     38        virtual UPnpCDSRootInfo *GetRootInfo   ( int nIdx, QString sContainerId = "" );
    3539        virtual int              GetRootCount  ( );
    3640        virtual QString          GetTableName  ( QString sColumn );
    3741        virtual QString          GetItemListSQL( QString sColumn = "" );
  • new file mythtv/programs/mythbackend/upnpcdsmusicalbum.cpp

    diff --git a/mythtv/programs/mythbackend/upnpcdsmusicalbum.cpp b/mythtv/programs/mythbackend/upnpcdsmusicalbum.cpp
    new file mode 100644
    index 0000000..256ea14
    - +  
     1//////////////////////////////////////////////////////////////////////////////
     2// Program Name: upnpcdsmusicalbum.cpp
     3//
     4// Purpose - uPnp Content Directory Extension for Music Albums
     5//
     6// Created By  : Joe Bryant                     Created On : Jul. 29, 2011
     7// Modified By :                                Modified On:
     8//
     9//////////////////////////////////////////////////////////////////////////////
     10
     11#include <climits>
     12
     13#include <QFileInfo>
     14
     15#include "upnpcdsmusicalbum.h"
     16#include "httprequest.h"
     17#include "mythcorecontext.h"
     18
     19
     20UPnpCDSRootInfo UPnpCDSMusicAlbum::g_RootNodes[] =
     21{
     22   
     23    {   "By Album",
     24        "album.album_id",
     25        "SELECT a.album_id as id, "
     26          "a.album_name as name, "
     27          "count( song.album_id ) as children "
     28            "FROM music_songs song join music_albums a on a.album_id = song.album_id "
     29            "%1 "
     30            "GROUP BY a.album_id "
     31            "ORDER BY a.album_name",
     32        "WHERE album.album_id=:KEY" },
     33
     34};
     35
     36int UPnpCDSMusicAlbum::g_nRootCount = sizeof( g_RootNodes ) / sizeof( UPnpCDSRootInfo );
     37
     38/////////////////////////////////////////////////////////////////////////////
     39//
     40/////////////////////////////////////////////////////////////////////////////
     41
     42UPnpCDSRootInfo *UPnpCDSMusicAlbum::GetRootInfo( int nIdx, QString sContainerId )
     43{
     44    if ((nIdx >=0 ) && ( nIdx < g_nRootCount ))
     45        return &(g_RootNodes[ nIdx ]);
     46
     47    return NULL;
     48}
     49
     50/////////////////////////////////////////////////////////////////////////////
     51//
     52/////////////////////////////////////////////////////////////////////////////
     53
     54int UPnpCDSMusicAlbum::GetRootCount()
     55{
     56    return g_nRootCount;
     57}
     58
     59/////////////////////////////////////////////////////////////////////////////
     60//
     61/////////////////////////////////////////////////////////////////////////////
     62
     63QString UPnpCDSMusicAlbum::GetTableName( QString sColumn )
     64{
     65    return "music_albums album";
     66}
     67
     68/////////////////////////////////////////////////////////////////////////////
     69//
     70/////////////////////////////////////////////////////////////////////////////
     71
     72QString UPnpCDSMusicAlbum::GetItemListSQL( QString /* sColumn */ )
     73{
     74    return "select album_id as intid, artist_name as artist, album_name as album " \
     75           "from music_albums " \
     76           "join music_artists " \
     77           "on music_albums.artist_id = music_artists.artist_id";
     78}
     79
     80/////////////////////////////////////////////////////////////////////////////
     81//
     82/////////////////////////////////////////////////////////////////////////////
     83
     84void UPnpCDSMusicAlbum::BuildItemQuery( MSqlQuery &query, const QStringMap &mapParams )
     85{
     86    int     nId    = mapParams[ "Id" ].toInt();
     87
     88    QString sSQL = QString( "%1 WHERE music_albums.album_id=:ID " )
     89                      .arg( GetItemListSQL() );
     90
     91    query.prepare( sSQL );
     92
     93    query.bindValue( ":ID", (int)nId );
     94}
     95
     96
     97/////////////////////////////////////////////////////////////////////////////
     98//
     99/////////////////////////////////////////////////////////////////////////////
     100
     101void UPnpCDSMusicAlbum::AddItem( const UPnpCDSRequest    *pRequest,
     102                            const QString           &sObjectId,
     103                            UPnpCDSExtensionResults *pResults,
     104                            bool                     bAddRef,
     105                            MSqlQuery               &query )
     106{
     107    QString sName;
     108
     109    QString sId     = QString( "musicalbum" ) + query.value( 0 ).toString();
     110    QString sArtist = query.value( 1 ).toString();
     111    QString sAlbum  = query.value( 2 ).toString();
     112
     113    CDSObject *pItem = CDSObject::CreateMusicAlbum( QString( sId ), sAlbum, QString( "1" ), NULL);
     114   
     115    pItem->SetPropValue( "artist", sArtist );
     116
     117    pResults->Add( pItem );
     118
     119}
     120
     121// vim:ts=4:sw=4:ai:et:si:sts=4
  • new file mythtv/programs/mythbackend/upnpcdsmusicalbum.h

    diff --git a/mythtv/programs/mythbackend/upnpcdsmusicalbum.h b/mythtv/programs/mythbackend/upnpcdsmusicalbum.h
    new file mode 100644
    index 0000000..e80fd86
    - +  
     1//////////////////////////////////////////////////////////////////////////////
     2// Program Name: upnpcdsmusicalbum.h
     3//
     4// Purpose - uPnp Content Directory Extension for Music Albums
     5//
     6// Created By  : Joe Bryant                     Created On : Jul. 29, 2011
     7// Modified By :                                Modified On:
     8//
     9//////////////////////////////////////////////////////////////////////////////
     10
     11#ifndef UPnpCDSMusicAlbum_H_
     12#define UPnpCDSMusicAlbum_H_
     13
     14#include <QString>
     15
     16#include "upnpcds.h"
     17
     18//////////////////////////////////////////////////////////////////////////////
     19//
     20//////////////////////////////////////////////////////////////////////////////
     21class MSqlQuery;
     22class UPnpCDSMusicAlbum : public UPnpCDSExtension
     23{
     24    private:
     25
     26        static UPnpCDSRootInfo g_RootNodes[];
     27        static int             g_nRootCount;
     28
     29    protected:
     30
     31        virtual UPnpCDSRootInfo *GetRootInfo   (int nIdx, QString sContainerId = "");
     32        virtual int              GetRootCount  ( );
     33        virtual QString          GetTableName  ( QString sColumn );
     34        virtual QString          GetItemListSQL( QString sColumn = "" );
     35
     36        virtual void             BuildItemQuery( MSqlQuery        &query,
     37                                                 const QStringMap &mapParams );
     38
     39        virtual void             AddItem( const UPnpCDSRequest    *pRequest,
     40                                          const QString           &sObjectId,
     41                                          UPnpCDSExtensionResults *pResults,
     42                                          bool                     bAddRef,
     43                                          MSqlQuery               &query );
     44    public:
     45
     46        UPnpCDSMusicAlbum( ) : UPnpCDSExtension( "Music", "Music",
     47                                            "object.container.album.musicAlbum" )
     48        {
     49        }
     50
     51        virtual ~UPnpCDSMusicAlbum() {}
     52};
     53
     54#endif
  • mythtv/programs/mythbackend/upnpcdstv.cpp

    diff --git a/mythtv/programs/mythbackend/upnpcdstv.cpp b/mythtv/programs/mythbackend/upnpcdstv.cpp
    index a29c2ff..6da5e66 100644
    a b int UPnpCDSTv::g_nRootCount = sizeof( g_RootNodes ) / sizeof( UPnpCDSRootInfo ); 
    111111//
    112112/////////////////////////////////////////////////////////////////////////////
    113113
    114 UPnpCDSRootInfo *UPnpCDSTv::GetRootInfo( int nIdx )
     114UPnpCDSRootInfo *UPnpCDSTv::GetRootInfo( int nIdx, QString sContainerId )
    115115{
    116116    if ((nIdx >=0 ) && ( nIdx < g_nRootCount ))
    117117        return &(g_RootNodes[ nIdx ]);
  • mythtv/programs/mythbackend/upnpcdstv.h

    diff --git a/mythtv/programs/mythbackend/upnpcdstv.h b/mythtv/programs/mythbackend/upnpcdstv.h
    index 9b4e620..b3970cc 100644
    a b class UPnpCDSTv : public UPnpCDSExtension 
    3232        virtual bool             IsBrowseRequestForUs( UPnpCDSRequest *pRequest );
    3333        virtual bool             IsSearchRequestForUs( UPnpCDSRequest *pRequest );
    3434
    35         virtual UPnpCDSRootInfo *GetRootInfo   (int nIdx);
     35        virtual UPnpCDSRootInfo *GetRootInfo   ( int nIdx, QString sContainerId = "" );
    3636        virtual int              GetRootCount  ( );
    3737        virtual QString          GetTableName  ( QString sColumn );
    3838        virtual QString          GetItemListSQL( QString sColumn = "" );
  • mythtv/programs/mythbackend/upnpcdsvideo.cpp

    diff --git a/mythtv/programs/mythbackend/upnpcdsvideo.cpp b/mythtv/programs/mythbackend/upnpcdsvideo.cpp
    index b0b12b3..88637a0 100644
    a b int UPnpCDSVideo::g_nRootCount = 1; 
    4444//
    4545/////////////////////////////////////////////////////////////////////////////
    4646
    47 UPnpCDSRootInfo *UPnpCDSVideo::GetRootInfo( int nIdx )
     47UPnpCDSRootInfo *UPnpCDSVideo::GetRootInfo( int nIdx, QString sContainerId )
    4848{
    4949    if ((nIdx >=0 ) && ( nIdx < g_nRootCount ))
    5050        return &(g_RootNodes[ nIdx ]);
  • mythtv/programs/mythbackend/upnpcdsvideo.h

    diff --git a/mythtv/programs/mythbackend/upnpcdsvideo.h b/mythtv/programs/mythbackend/upnpcdsvideo.h
    index 56e98d5..b547035 100644
    a b class UPnpCDSVideo : public UPnpCDSExtension 
    3737
    3838        virtual int              GetDistinctCount( UPnpCDSRootInfo *pInfo );
    3939
    40         virtual UPnpCDSRootInfo *GetRootInfo   (int nIdx);
     40        virtual UPnpCDSRootInfo *GetRootInfo   ( int nIdx, QString sContainerId = "" );
    4141        virtual int              GetRootCount  ( );
    4242        virtual QString          GetTableName  ( QString sColumn );
    4343        virtual QString          GetItemListSQL( QString sColumn = "");
  • mythtv/programs/programs-libs.pro

    diff --git a/mythtv/programs/programs-libs.pro b/mythtv/programs/programs-libs.pro
    index d6c936c..4633c76 100644
    a b LIBS += -L../../external/FFmpeg/libavutil 
    1313LIBS += -L../../external/FFmpeg/libavcodec
    1414LIBS += -L../../external/FFmpeg/libavformat
    1515LIBS += -L../../external/FFmpeg/libswscale
     16LIBS += -L../../external/FFmpeg/libavdevice
    1617LIBS += -L../../libs/libmythbase
    1718LIBS += -L../../libs/libmythui
    1819LIBS += -L../../libs/libmythupnp
    LIBS += -lmythtv-$$LIBVERSION 
    2425LIBS += -lmythswscale
    2526LIBS += -lmythavformat
    2627LIBS += -lmythavcodec
     28LIBS += -lmythavdevice
    2729LIBS += -lmythavutil
    2830LIBS += -lmythupnp-$$LIBVERSION
    2931LIBS += -lmythbase-$$LIBVERSION