【问题标题】:Playing wav file with portaudio and sndfile使用 portaudio 和 sndfile 播放 wav 文件
【发布时间】:2015-05-28 18:27:39
【问题描述】:

我编写了一个函数来使用 portaudio 和 sndfile 播放声音文件。不幸的是,音质很糟糕。声音更像是嘶嘶声。以下是我正在使用的函数的源代码。

#define _GLIBCXX_USE_C99_MATH 1
#include "PlaySound_config.h"
#include <boost/predef.h>

#if !defined(USE_PORTAUDIO) // {
#  define USE_PORTAUDIO 0
#  if (! BOOST_OS_CYGWIN && ! BOOST_OS_WINDOWS) // {
#    undef USE_PORTAUDIO
#    define USE_PORTAUDIO 1
#  endif // }
#endif // }

#if (PLAY_SOUND_HAVE_PORTAUDIO_H && PLAY_SOUND_HAVE_SNDFILE_H && PLAY_SOUND_HAVE_SNDFILE_HH && USE_PORTAUDIO) // {

#if (PLAY_SOUND_HAVE_UNISTD_H)
#  include <unistd.h>
#endif

#include <errno.h>
#include <stdio.h>
#include <stdlib.h>

#include <portaudio.h>
#include <sndfile.hh>

#include <cmath>
#include <fstream>
#include <iostream>
#include <vector>

#include <boost/filesystem/operations.hpp>
#include <boost/filesystem/path.hpp>
#include <boost/format.hpp>

#include "PlaySound.h"
#include "PlaySoundStrings.h"

void SoundWarning(const std::string& message)
{
    std::cerr << message << std::endl;
}

bool PlaySoundFile(const std::string& soundFile, unsigned long /* volume */)
{
    const int MAX_CHANNELS = 1;
    const double SAMPLE_RATE = 11025.0;
    const unsigned long FRAMES_PER_BUFFER = 1024;
    const size_t BUFFER_LEN = 1024;
    using boost::format;
    using boost::io::group;
    std::string message;
    if (soundFile.empty())
    {
        errno = EINVAL;
        message = playSoundStrings[error_invalid_argument];
        SoundWarning(message);
        return false;
    }
    boost::filesystem::path soundFilePath(soundFile);
    if (! boost::filesystem::exists(soundFilePath))
    {
        errno = EINVAL;
        message = str(format(playSoundStrings[error_file_does_not_exist]) % soundFile.c_str());
        SoundWarning(message);
        return false;
    }
    PaError paError = Pa_Initialize();
    if (paError != paNoError)
    {
        message = str(format(playSoundStrings[error_pa_initialize_failed]) % Pa_GetErrorText(paError));
        SoundWarning(message);
        return false;
    }
    SNDFILE* sndFile;
    SF_INFO sfInfo;
    sndFile = sf_open(soundFile.c_str(), SFM_READ, &sfInfo);
    if (! sndFile)
    {
        message = str(format(playSoundStrings[error_sf_open_failed]) % soundFile.c_str() % sf_strerror(nullptr));
        SoundWarning(message);
        Pa_Terminate();
        return false;
    }
    if (sfInfo.channels > MAX_CHANNELS)
    {
        message = str(format(playSoundStrings[error_too_many_channels]) % sfInfo.channels % MAX_CHANNELS);
        SoundWarning(message);
        Pa_Terminate();
        return false;
    }
    PaStream* stream = nullptr;
    PaStreamParameters paStreamParameters;
    paStreamParameters.device = Pa_GetDefaultOutputDevice();
    paStreamParameters.channelCount = sfInfo.channels;
    paStreamParameters.sampleFormat = paInt16;
    paStreamParameters.suggestedLatency = Pa_GetDeviceInfo(paStreamParameters.device)->defaultLowOutputLatency;
    paStreamParameters.hostApiSpecificStreamInfo = nullptr;
    paError = Pa_OpenStream(
        &stream, nullptr, &paStreamParameters,
        SAMPLE_RATE, FRAMES_PER_BUFFER, paClipOff,
        nullptr, nullptr);
    if (paError != paNoError || ! stream)
    {
        message = str(format(playSoundStrings[error_pa_open_stream_failed]) % Pa_GetErrorText(paError));
        SoundWarning(message);
        Pa_Terminate();
        return false;
    }
    paError = Pa_StartStream(stream);
    if (paError != paNoError)
    {
        message = str(format(playSoundStrings[error_pa_start_stream_failed]) % Pa_GetErrorText(paError));
        SoundWarning(message);
        Pa_Terminate();
        return false;
    }
    sf_count_t readCount = 0;
    double data[BUFFER_LEN];
    while ((readCount = sf_read_double(sndFile, data, BUFFER_LEN)))
    {
        paError = Pa_WriteStream(stream, data, BUFFER_LEN);
        if (paError != paNoError)
        {
            message = str(format(playSoundStrings[error_pa_write_stream_failed]) % Pa_GetErrorText(paError));
            SoundWarning(message);
            break;
        }
    }
    paError = Pa_CloseStream(stream);
    if (paError != paNoError)
    {
        message = str(format(playSoundStrings[error_pa_close_stream_failed]) % Pa_GetErrorText(paError));
        SoundWarning(message);
        Pa_Terminate();
        return false;
    }
    Pa_Terminate();
    return true;
}

我在文章What is a lightweight cross platform WAV playing library? 中看到了一些示例代码,但示例不完整。它似乎只会播放文件的前五秒。我想播放整个文件。

知道我做错了什么吗?

此代码是我的PlaySound 项目的一部分。

【问题讨论】:

    标签: c++ portaudio libsndfile


    【解决方案1】:

    我在我的代码的原始版本中犯了几个错误。第一个是在我初始化 PaStreamParameters 结构的 sampleFormat 成员的那一行。

    在我的原始代码中,我按如下方式初始化了这个成员。

    paStreamParameters.sampleFormat = paInt16;
    

    我应该如下初始化它。

    paStreamParameters.sampleFormat = paInt32;
    

    我的下一个错误是调用 Pa_OpenStream 函数。我将 sampleRate 参数设置为硬编码常量,在本例中为 11025.0。我应该将它设置为 SF_INFO 结构的 samplerate 成员的值。

    我的第三个错误是使用 sf_read_double 函数读取声音文件。在我最终发现的几个工作示例中,包括 sndfile-play 应用程序,都使用了 sf_read_float 函数。

    我的第四个错误是在将声音文件读取的数据传递给 Pa_WriteStream 函数之前,我没有缩放它。我在 sndfile-play 应用程序的源代码中找到了缩放数据的代码。

    有兴趣的朋友,我的源代码最终版本如下。

    #define _GLIBCXX_USE_C99_MATH 1
    #include "PlaySound_config.h"
    #include <boost/predef.h>
    
    #if !defined(USE_PORTAUDIO) // {
    #  define USE_PORTAUDIO 0
    #  if (! BOOST_OS_CYGWIN && ! BOOST_OS_WINDOWS) // {
    #    undef USE_PORTAUDIO
    #    define USE_PORTAUDIO 1
    #  endif // }
    #endif // }
    
    #if (PLAY_SOUND_HAVE_PORTAUDIO_H && PLAY_SOUND_HAVE_SNDFILE_H && PLAY_SOUND_HAVE_SNDFILE_HH && USE_PORTAUDIO) // {
    
    #if (PLAY_SOUND_HAVE_UNISTD_H)
    #  include <unistd.h>
    #endif
    
    #include <errno.h>
    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    
    #include <portaudio.h>
    #include <sndfile.hh>
    
    #include <cmath>
    #include <fstream>
    #include <iostream>
    #include <vector>
    
    #include <boost/filesystem/operations.hpp>
    #include <boost/filesystem/path.hpp>
    #include <boost/format.hpp>
    
    #include "PlaySound.h"
    #include "PlaySoundStrings.h"
    
    void SoundWarning(const std::string& message)
    {
        std::cerr << message << std::endl;
    }
    
    bool PlaySoundFile(const std::string& soundFile, unsigned long /* volume */)
    {
        const int MAX_CHANNELS = 1;
        const size_t BUFFER_LEN = 1024;
        using boost::format;
        using boost::io::group;
        std::string message;
        if (soundFile.empty())
        {
            errno = EINVAL;
            message = playSoundStrings[error_invalid_argument];
            SoundWarning(message);
            return false;
        }
        boost::filesystem::path soundFilePath(soundFile);
        if (! boost::filesystem::exists(soundFilePath))
        {
            errno = EINVAL;
            message = str(format(playSoundStrings[error_file_does_not_exist]) % soundFile.c_str());
            SoundWarning(message);
            return false;
        }
        PaError paError = Pa_Initialize();
        if (paError != paNoError)
        {
            message = str(format(playSoundStrings[error_pa_initialize_failed]) % Pa_GetErrorText(paError));
            SoundWarning(message);
            return false;
        }
        SNDFILE* sndFile = nullptr;
        SF_INFO sfInfo;
        ::memset(&sfInfo, 0, sizeof(sfInfo));
        sndFile = sf_open(soundFile.c_str(), SFM_READ, &sfInfo);
        if (! sndFile)
        {
            message = str(format(playSoundStrings[error_sf_open_failed]) % soundFile.c_str() % sf_strerror(nullptr));
            SoundWarning(message);
               Pa_Terminate();
            return false;
        }
        if (sfInfo.channels > MAX_CHANNELS)
        {
            message = str(format(playSoundStrings[error_too_many_channels]) % sfInfo.channels % MAX_CHANNELS);
            SoundWarning(message);
            Pa_Terminate();
            return false;
        }
        PaStream* stream = nullptr;
        PaStreamParameters paStreamParameters;
        paStreamParameters.device = Pa_GetDefaultOutputDevice();
        paStreamParameters.channelCount = sfInfo.channels;
        paStreamParameters.sampleFormat = paInt32;
        paStreamParameters.suggestedLatency = Pa_GetDeviceInfo(paStreamParameters.device)->defaultLowOutputLatency;
        paStreamParameters.hostApiSpecificStreamInfo = nullptr;
        paError = Pa_OpenStream(
            &stream, nullptr, &paStreamParameters,
            sfInfo.samplerate, paFramesPerBufferUnspecified, paClipOff,
            nullptr, nullptr);
        if (paError != paNoError || ! stream)
        {
            message = str(format(playSoundStrings[error_pa_open_stream_failed]) % Pa_GetErrorText(paError));
            SoundWarning(message);
            Pa_Terminate();
            return false;
        }
        paError = Pa_StartStream(stream);
        if (paError != paNoError)
        {
            message = str(format(playSoundStrings[error_pa_start_stream_failed]) % Pa_GetErrorText(paError));
            SoundWarning(message);
            Pa_Terminate();
            return false;
        }
        int subFormat = sfInfo.format & SF_FORMAT_SUBMASK;
        double scale = 1.0;
        if (subFormat == SF_FORMAT_FLOAT || subFormat == SF_FORMAT_DOUBLE)
        {
            sf_command(sndFile, SFC_CALC_SIGNAL_MAX, &scale, sizeof(scale));
            if (scale < 1e-10)
            {
                scale = 1.0;
            }
            else
            {
                scale = 32700.0 / scale;
            }
        }
        sf_count_t readCount = 0;
        float data[BUFFER_LEN];
        ::memset(data, 0, sizeof(data));
        while ((readCount = sf_read_float(sndFile, data, BUFFER_LEN)))
        {
            if (subFormat == SF_FORMAT_FLOAT || subFormat == SF_FORMAT_DOUBLE)
            {
                int m = 0;
                for (m = 0 ; m < readCount ; ++m)
                {
                    data[m] *= scale;
                }
            }
            paError = Pa_WriteStream(stream, data, BUFFER_LEN);
            if (paError != paNoError)
            {
                message = str(format(playSoundStrings[error_pa_write_stream_failed]) % Pa_GetErrorText(paError));
                SoundWarning(message);
                break;
            }
            ::memset(data, 0, sizeof(data));
        }
        paError = Pa_CloseStream(stream);
        if (paError != paNoError)
        {
            message = str(format(playSoundStrings[error_pa_close_stream_failed]) % Pa_GetErrorText(paError));
            SoundWarning(message);
            Pa_Terminate();
            return false;
        }
        Pa_Terminate();
        return true;
    }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2020-08-26
      • 2012-12-09
      • 1970-01-01
      • 1970-01-01
      • 2012-08-03
      • 2012-04-16
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多