aboutsummaryrefslogblamecommitdiff
path: root/src/plugins/General/rgscan/rgscanner.cpp
blob: 02560734372dac1713f2f80c253868cd1c265a1a (plain) (tree)
1
2
3
                                                                            
                                                                            
                                                                            
















                                                                             
                      
                 

                                    
                                
                      
 

                          
                      
 
                
                

                         
                         
                         


                        

 
                       

           
             

                
                                     
                           
     

 
                                           
 
                         
             
                
                                          
                                                            


                             
                                           


                     
                                                                            
     
                                                                          

                                                                
                      
                     

     
                                                                      


                
                                                                             


                      

                                                                   



                                                           
                                                                           

                              
                                                                          



                       

                                                 
                                                                  





                                                        

                        
                        
                         
                        


                
                      
 
                   

                       

 
                                 



                        




                                 
                                 



                        







                                                                      
                              

                  

 
                              



                  
                              



                 
                                 



                    
                     
 
                   

                             
               
     
                                          
                                                               
                        
                         
                       
 
                                                      


                                     
                                                                                               
                                                                
                                                   
                                                   





                                                                                                   
                   
 
                
                                     
                                                 


           
                                                  
 
                   
         




                         
 
                                        
 
                                                        
 


                                                    
             



                                                        
             



                                             
             

                                                   
             
         
 




                                                                                          
 







                             
 

             
                                                                          


                        
                                                                    





                                        

                                                                                     


                            
                         
                         
 
 
                        



                         
                            



                        
                           

     
/***************************************************************************
 *   Copyright (C) 2013-2020 by Ilya Kotov                                 *
 *   forkotov02@ya.ru                                                      *
 *                                                                         *
 *   This program is free software; you can redistribute it and/or modify  *
 *   it under the terms of the GNU General Public License as published by  *
 *   the Free Software Foundation; either version 2 of the License, or     *
 *   (at your option) any later version.                                   *
 *                                                                         *
 *   This program is distributed in the hope that it will be useful,       *
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
 *   GNU General Public License for more details.                          *
 *                                                                         *
 *   You should have received a copy of the GNU General Public License     *
 *   along with this program; if not, write to the                         *
 *   Free Software Foundation, Inc.,                                       *
 *   51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.         *
 ***************************************************************************/

#include <QStringList>
#include <math.h>
#include <qmmp/inputsourcefactory.h>
#include <qmmp/decoderfactory.h>
#include <qmmp/audioconverter.h>
#include "rgscanner.h"

#define BUFFER_FRAMES 4096

RGScanner::RGScanner()
{
    m_gain = 0.;
    m_peak = 0.;
    m_user_stop = false;
    m_is_running = false;
    m_is_pending = false;
    m_has_values = false;
    m_handle = nullptr;
    m_decoder = nullptr;
    m_source = nullptr;
}

RGScanner::~RGScanner()
{
    stop();
    deinit();
    if(m_handle)
    {
        DeinitGainAnalysis(m_handle);
        m_handle = nullptr;
    }
}

bool RGScanner::prepare(const QString &url)
{
    m_is_pending = false;
    deinit();
    m_url = url;
    QString name = m_url.section("/", -1);
    InputSource *source = InputSource::create(url, nullptr);
    if(!source->initialize())
    {
        delete source;
        qWarning("RGScanner: Invalid url");
        return false;
    }

    if(source->ioDevice() && !source->ioDevice()->open(QIODevice::ReadOnly))
    {
        qWarning("RGScanner: [%s] unable to open input stream, error: %s",
                 qPrintable(name),
                 qPrintable(source->ioDevice()->errorString()));
        delete source;
        return false;
    }

    DecoderFactory *factory = Decoder::findByFilePath(source->path());

    if(!factory)
    {
        qWarning("RGScanner: [%s] unable to find factory", qPrintable(name));
        delete source;
        return false;
    }
    qDebug("RGScanner: [%s] selected decoder: %s",qPrintable(name),
           qPrintable(factory->properties().shortName));

    if(factory->properties().noInput && source->ioDevice())
        source->ioDevice()->close();

    Decoder *decoder = factory->create(source->path(), source->ioDevice());
    if(!decoder->initialize())
    {
        qWarning("RGScanner: [%s] invalid file format", qPrintable(name));
        delete source;
        delete decoder;
        return false;
    }
    if(decoder->audioParameters().channels() > 2)
    {
        qWarning("RGScanner: [%s] unsupported channel number: %d",
                 qPrintable(name),
                 decoder->audioParameters().channels());
        delete source;
        delete decoder;
        return false;
    }
    m_decoder = decoder;
    m_source = source;
    m_user_stop = false;
    m_has_values = false;
    m_is_pending = true;
    return true;
}

void RGScanner::stop()
{
    m_mutex.lock();
    m_user_stop = true;
    m_mutex.unlock();
}

bool RGScanner::isRunning() const
{
    return m_is_running;
}

bool RGScanner::isPending() const
{
    return m_is_pending;
}

bool RGScanner::hasValues() const
{
    return m_has_values;
}

QMap<Qmmp::ReplayGainKey, double> RGScanner::oldReplayGainInfo() const
{
    if(!m_decoder)
        return QMap<Qmmp::ReplayGainKey, double>();

    return m_decoder->replayGainInfo();
}

double RGScanner::gain() const
{
    return m_gain;
}

double RGScanner::peak() const
{
    return m_peak;
}

QString RGScanner::url() const
{
    return m_url;
}

GainHandle_t *RGScanner::handle()
{
    return m_handle;
}

void RGScanner::run()
{
    if(m_user_stop)
    {
        m_is_pending = false;
        return;
    }
    QString name = m_url.section("/", -1);
    qDebug("RGScanner: [%s] staring thread", qPrintable(name));
    m_is_running = true;
    m_is_pending = false;
    bool error = false;

    AudioParameters ap = m_decoder->audioParameters();
    AudioConverter converter;
    converter.configure(ap.format());
    //buffers
    double out_left[BUFFER_FRAMES] = {0}, out_right[BUFFER_FRAMES] = {0}; //replay gain buffers
    float float_buf[BUFFER_FRAMES*ap.channels()]; //float buffer
    qint64 buf_size = BUFFER_FRAMES*ap.frameSize();
    unsigned char char_buf[buf_size]; //char buffer


    //counters
    qint64 totalSamples = m_decoder->totalTime() * ap.sampleRate() * ap.channels() / 1000, len = 0;
    quint64 sample_counter = 0;
    quint64 samples = 0;
    double max = 0;

    if(m_handle)
        DeinitGainAnalysis(m_handle);
    InitGainAnalysis(&m_handle, ap.sampleRate());

    forever
    {
        len = m_decoder->read(char_buf, buf_size);

        if(len < 0)
        {
            error = true;
            break;
        }
        else if(len == 0)
            break;

        samples = len / ap.sampleSize();

        converter.toFloat(char_buf, float_buf, samples);

        if(ap.channels() == 2)
        {
            for(uint i = 0; i < (samples >> 1); ++i)
            {
                out_left[i] = float_buf[i*2]*32768.0;
                out_right[i] = float_buf[i*2+1]*32768.0;
                max = qMax(fabs(out_left[i]), max);
                max = qMax(fabs(out_right[i]), max);
            }
        }
        else if(ap.channels() == 1)
        {
            for(uint i = 0; i < samples; ++i)
            {
                out_left[i] = float_buf[i]*32768.0;
                max = qMax(fabs(out_left[i]), max);
            }
        }

        size_t samples_per_channel = samples >> ((ap.channels() == 2) ? 1 : 0);

        AnalyzeSamples(m_handle, out_left, out_right, samples_per_channel, ap.channels());
        sample_counter += samples;
        emit progress(100 * sample_counter / totalSamples);

        m_mutex.lock();
        if(m_user_stop)
        {
            m_mutex.unlock();
            break;
        }
        m_mutex.unlock();
    }

    if(error)
    {
        qWarning("RGScanner: [%s] finished with error", qPrintable(name));
    }
    else if(m_user_stop)
    {
        qDebug("RGScanner: [%s] stopped by user", qPrintable(name));
    }
    else
    {
        m_gain = GetTitleGain(m_handle);
        m_peak = max/32768.0;
        emit progress(100);
        qDebug("RGScanner: [%s] peak=%f, gain=%f", qPrintable(name), m_peak, m_gain);
        qDebug("RGScanner: [%s] finished with success ", qPrintable(name));
        m_has_values = true;
    }
    deinit();
    m_is_running = false;
    emit finished(m_url);
}

void RGScanner::deinit()
{
    if(m_decoder)
    {
        delete m_decoder;
        m_decoder = nullptr;
    }
    if(m_source)
    {
        delete m_source;
        m_source = nullptr;
    }
}