使用OpenAL和FFMPEG解码并播放音乐
OpenAL是一个开源的音效库,然而这里只用它来播放音乐。 FFMPEG负责把音乐解码并转换成指定的编码和采样率,然后送到OpenAL中播放。 (已在windows和ios平台简单测试通过)
AudioDecoder.h
#ifndef __AUDIODECODER_H__
#define __AUDIODECODER_H__
extern "C"
{
#include <libavcodec/avcodec.h>
#include <libavformat/avformat.h>
#include <libavutil/log.h>
#include <libavutil/frame.h>
#include <libswscale/swscale.h>
#include <libswresample/swresample.h>
#include <libavutil/samplefmt.h>
#include <libavutil/channel_layout.h>
}
#include <string>
#include <queue>
#include <thread>
#include <mutex>
#include <condition_variable>
#include <atomic>
typedef struct _tFrame
{
void* data;
int size;
int chs;
int samplerate;
uint64_t pts;
uint64_t duration;
}TFRAME, *PTFRAME;
class AudioDecoder
{
public:
AudioDecoder(
int outSamplerate =
44100,
int outChannels =
2);
~AudioDecoder();
int OpenFile(
std::
string path);
int GetChs() {
return m_ch; };
int GetSampleRate() {
return m_sampleRate; };
uint64_t GetWholeDuration() {
return m_wholeDuration; };
int StartDecode();
int StopDecode();
PTFRAME GetFrame();
void SetSeekPosition(
double playProcess);
private:
int DecodeThread();
int InternalAudioSeek(uint64_t start_time);
private:
int m_ch;
int m_sampleRate;
uint64_t m_wholeDuration;
std::
shared_ptr<AVFormatContext> m_pFormatCtx;
AVCodecContext* m_pAudioCodecCtx;
AVCodec* m_pAudioCodec;
int m_nAudioIndex;
std::
string m_strPath;
std::
queue<PTFRAME> m_queueData;
std::atomic_bool m_bDecoding;
static const int MAX_BUFF_SIZE =
128;
std::
shared_ptr<
std::mutex> m_pmtx;
std::
shared_ptr<
std::condition_variable> m_pcond;
std::
shared_ptr<
std::thread> m_pDecode;
std::atomic_bool m_bStop;
std::atomic_bool m_bSeeked;
int m_outSampleRate;
int m_outChs;
};
#endif
AudioDecoder.cpp
#include "stdafx.h"
#include "AudioDecoder.h"
#define CPP_TIME_BASE_Q (AVRational{1, AV_TIME_BASE})
AudioDecoder::AudioDecoder(
int outSamplerate,
int outChannels)
{
m_pmtx =
nullptr;
m_pAudioCodec =
nullptr;
m_pFormatCtx =
nullptr;
m_pAudioCodecCtx =
nullptr;
static bool bFFMPEGInit =
false;
if (!bFFMPEGInit)
{
av_register_all();
avcodec_register_all();
bFFMPEGInit =
true;
}
m_outSampleRate = outSamplerate;
m_outChs = outChannels;
m_wholeDuration = -
1;
m_ch = -
1;
m_strPath =
"";
m_bStop =
true;
m_bSeeked =
false;
}
AudioDecoder::~AudioDecoder()
{
}
int AudioDecoder::OpenFile(
std::
string path)
{
if (!m_bStop)
{
StopDecode();
}
m_strPath = path;
AVFormatContext* pFormatCtx =
nullptr;
if (
0 != avformat_open_input( &pFormatCtx, path.c_str(), NULL, NULL))
{
return -
1;
}
m_pFormatCtx =
std::
shared_ptr<AVFormatContext>(pFormatCtx, [](AVFormatContext* pf) {
if (pf) avformat_close_input(&pf); });
if (avformat_find_stream_info(m_pFormatCtx.get(), NULL) <
0)
{
return -
1;
}
for (
int i =
0; i < m_pFormatCtx->nb_streams; i++)
{
if (m_pFormatCtx->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_AUDIO)
{
m_nAudioIndex = i;
break;
}
}
if (m_nAudioIndex == -
1)
{
return -
1;
}
m_pAudioCodecCtx = m_pFormatCtx->streams[m_nAudioIndex]->codec;
m_pAudioCodec = avcodec_find_decoder(m_pAudioCodecCtx->codec_id);
if (m_pAudioCodec <
0)
{
return -
1;
}
if (avcodec_open2(m_pAudioCodecCtx, m_pAudioCodec, NULL) <
0)
{
return -
1;
}
m_ch = m_pAudioCodecCtx->channels;
m_wholeDuration = av_rescale_q(m_pFormatCtx->streams[m_nAudioIndex]->duration,
m_pFormatCtx->streams[m_nAudioIndex]->time_base,
CPP_TIME_BASE_Q);
return 0;
}
int AudioDecoder::StartDecode()
{
m_bStop =
false;
m_pmtx =
std::move(
std::make_shared<
std::mutex>());
m_pcond =
std::move(
std::make_shared<
std::condition_variable>());
m_pDecode =
std::move(
std::make_shared<
std::thread>(&AudioDecoder::DecodeThread,
this));
return 0;
}
int AudioDecoder::StopDecode()
{
m_bStop =
true;
m_pcond->notify_all();
if(m_pDecode->joinable())
m_pDecode->join();
std::unique_lock<
std::mutex> lck(*m_pmtx);
int queueSize = m_queueData.size();
for (
int i = queueSize -
1; i >=
0; i--)
{
PTFRAME f = m_queueData.front();
m_queueData.pop();
if (f)
{
if (f->data)
av_free(f->data);
delete f;
}
}
return 0;
}
PTFRAME AudioDecoder::GetFrame()
{
PTFRAME frame =
nullptr;
std::unique_lock<
std::mutex> lck(*m_pmtx);
m_pcond->wait(lck,
[
this]() {
return m_bStop || m_queueData.size() >
0; });
if (!m_bStop)
{
frame = m_queueData.front();
m_queueData.pop();
}
lck.unlock();
m_pcond->notify_one();
return frame;
}
int AudioDecoder::DecodeThread()
{
if (m_strPath ==
"")
return -
1;
m_bDecoding =
true;
AVPacket *packet = (AVPacket *)av_malloc(
sizeof(AVPacket));
av_init_packet(packet);
std::
shared_ptr<AVFrame> pAudioFrame(av_frame_alloc(), [](AVFrame* pFrame) {av_frame_free(&pFrame);});
int64_t in_channel_layout = av_get_default_channel_layout(m_pAudioCodecCtx->channels);
struct SwrContext* au_convert_ctx = swr_alloc();
int64_t outLayout;
if (m_outChs ==
1)
{
outLayout = AV_CH_LAYOUT_MONO;
}
else
{
outLayout = AV_CH_LAYOUT_STEREO;
}
au_convert_ctx = swr_alloc_set_opts(au_convert_ctx,
outLayout, AV_SAMPLE_FMT_S16,
m_outSampleRate, in_channel_layout,
m_pAudioCodecCtx->sample_fmt, m_pAudioCodecCtx->sample_rate,
0,
NULL);
swr_init(au_convert_ctx);
while (
true)
{
int ret = av_read_frame(m_pFormatCtx.get(), packet);
if (ret <
0)
break;
if (packet->stream_index == m_nAudioIndex)
{
int nAudioFinished =
0;
int nRet = avcodec_decode_audio4(m_pAudioCodecCtx, pAudioFrame.get(),
&nAudioFinished, packet);
if (nRet >
0 && nAudioFinished !=
0)
{
PTFRAME frame =
new TFRAME;
frame->chs = m_outChs;
frame->samplerate = m_outSampleRate;
frame->duration = av_rescale_q(packet->duration, m_pFormatCtx->streams[m_nAudioIndex]->time_base, CPP_TIME_BASE_Q);
frame->pts = av_rescale_q(packet->pts, m_pFormatCtx->streams[m_nAudioIndex]->time_base, CPP_TIME_BASE_Q);
int outSizeCandidate = m_outSampleRate *
8 *
double(frame->duration) /
1000000.0;
uint8_t* convertData = (uint8_t*)av_malloc(
sizeof(uint8_t) * outSizeCandidate);
int out_samples = swr_convert(au_convert_ctx,
&convertData, outSizeCandidate,
(
const uint8_t**)&pAudioFrame->data[
0], pAudioFrame->nb_samples);
int Audiobuffer_size = av_samples_get_buffer_size(NULL,
m_outChs, out_samples,AV_SAMPLE_FMT_S16,
1);
frame->data = convertData;
frame->size = Audiobuffer_size;
std::unique_lock<
std::mutex> lck(*m_pmtx);
m_pcond->wait(lck,
[
this]() {
return m_bStop || m_queueData.size() < MAX_BUFF_SIZE; });
if (m_bStop)
{
av_free_packet(packet);
break;
}
if (m_bSeeked)
{
m_bSeeked =
false;
av_free_packet(packet);
continue;
}
m_queueData.push(frame);
lck.unlock();
m_pcond->notify_one();
}
}
av_free_packet(packet);
}
swr_free(&au_convert_ctx);
return 0;
}
int AudioDecoder::InternalAudioSeek(uint64_t start_time_in_us)
{
if (start_time_in_us <
0)
{
return -
1;
}
int64_t seek_pos = start_time_in_us;
if (m_pFormatCtx->start_time != AV_NOPTS_VALUE)
seek_pos += m_pFormatCtx->start_time;
if (av_seek_frame(m_pFormatCtx.get(), -
1, seek_pos, AVSEEK_FLAG_BACKWARD) <
0)
{
return -
2;
}
avcodec_flush_buffers(m_pFormatCtx->streams[m_nAudioIndex]->codec);
return 0;
}
void AudioDecoder::SetSeekPosition(
double playProcess)
{
uint64_t pos = (uint64_t)(playProcess * (
double)m_wholeDuration);
std::unique_lock<
std::mutex> lck(*m_pmtx);
m_bSeeked =
true;
InternalAudioSeek(pos);
int queueSize = m_queueData.size();
for (
int i = queueSize -
1; i >=
0; i--)
{
PTFRAME f = m_queueData.front();
m_queueData.pop();
if (f)
{
if (f->data)
av_free(f->data);
delete f;
}
}
m_pcond->notify_all();
return;
}
ALEngine.h
#ifndef __ALENGINE_H__
#define __ALENGINE_H__
#define NUMBUFFERS (4)
#define SERVICE_UPDATE_PERIOD (20)
#include <al.h>
#include <alc.h>
#include <thread>
#include <mutex>
#include <condition_variable>
#include <atomic>
#include "AudioDecoder.h"
class ALEngine
{
public:
ALEngine();
~ALEngine();
int OpenFile(std::
string path);
int Play();
int PausePlay();
int Pause();
int Stop();
private:
ALuint m_source;
std::unique_ptr<AudioDecoder> m_decoder;
std::unique_ptr<std::thread> m_ptPlaying;
std::atomic_bool m_bStop;
ALuint m_buffers[NUMBUFFERS];
ALuint m_bufferTemp;
private:
int SoundPlayingThread();
int SoundCallback(ALuint& bufferID);
int InitEngine();
int DestroyEngine();
};
#endif
ALEngine.cpp
#include "stdafx.h"
#include "ALEngine.h"
ALEngine::ALEngine()
{
InitEngine();
m_bStop =
true;
}
ALEngine::~ALEngine()
{
DestroyEngine();
}
int ALEngine::InitEngine()
{
ALCdevice* pDevice;
ALCcontext* pContext;
pDevice = alcOpenDevice(NULL);
pContext = alcCreateContext(pDevice, NULL);
alcMakeContextCurrent(pContext);
if (alcGetError(pDevice) != ALC_NO_ERROR)
return AL_FALSE;
return 0;
}
int ALEngine::DestroyEngine()
{
ALCcontext* pCurContext;
ALCdevice* pCurDevice;
pCurContext = alcGetCurrentContext();
pCurDevice = alcGetContextsDevice(pCurContext);
alcMakeContextCurrent(NULL);
alcDestroyContext(pCurContext);
alcCloseDevice(pCurDevice);
return 0;
}
int ALEngine::OpenFile(
std::
string path)
{
if (!m_bStop)
{
Stop();
}
m_bStop =
false;
alGenSources(
1, &m_source);
if (alGetError() != AL_NO_ERROR)
{
printf(
"Error generating audio source.");
return -
1;
}
ALfloat SourcePos[] = {
0.0,
0.0,
0.0 };
ALfloat SourceVel[] = {
0.0,
0.0,
0.0 };
ALfloat ListenerPos[] = {
0.0,
0,
0 };
ALfloat ListenerVel[] = {
0.0,
0.0,
0.0 };
ALfloat ListenerOri[] = {
0.0,
0.0, -
1.0,
0.0,
1.0,
0.0 };
alSourcef(m_source, AL_PITCH,
1.0);
alSourcef(m_source, AL_GAIN,
1.0);
alSourcefv(m_source, AL_POSITION, SourcePos);
alSourcefv(m_source, AL_VELOCITY, SourceVel);
alSourcef(m_source, AL_REFERENCE_DISTANCE,
50.0f);
alSourcei(m_source, AL_LOOPING, AL_FALSE);
alDistanceModel(AL_LINEAR_DISTANCE_CLAMPED);
alListener3f(AL_POSITION,
0,
0,
0);
m_decoder =
std::move(
std::make_unique<AudioDecoder>());
m_decoder->OpenFile(path);
m_decoder->StartDecode();
alGenBuffers(NUMBUFFERS, m_buffers);
m_ptPlaying.reset(
new std::thread(&ALEngine::SoundPlayingThread,
this));
return 0;
}
int ALEngine::Play()
{
int state;
alGetSourcei(m_source, AL_SOURCE_STATE, &state);
if (state == AL_STOPPED || state == AL_INITIAL)
{
alSourcePlay(m_source);
}
return 0;
}
int ALEngine::PausePlay()
{
int state;
alGetSourcei(m_source, AL_SOURCE_STATE, &state);
if (state == AL_PAUSED)
{
alSourcePlay(m_source);
}
return 0;
}
int ALEngine::Pause()
{
alSourcePause(m_source);
return 0;
}
int ALEngine::Stop()
{
if(m_bStop)
return 0;
m_bStop =
true;
alSourceStop(m_source);
alSourcei(m_source, AL_BUFFER,
0);
m_decoder->StopDecode();
if(m_ptPlaying->joinable())
m_ptPlaying->join();
alDeleteBuffers(NUMBUFFERS, m_buffers);
alDeleteSources(
1, &m_source);
return 0;
}
int ALEngine::SoundPlayingThread()
{
for (
int i =
0; i < NUMBUFFERS; i++)
{
SoundCallback(m_buffers[i]);
}
Play();
while (
true)
{
if (m_bStop)
break;
std::this_thread::sleep_for(
std::chrono::milliseconds(SERVICE_UPDATE_PERIOD));
ALint processed =
0;
alGetSourcei(m_source, AL_BUFFERS_PROCESSED, &processed);
while (processed >
0)
{
ALuint bufferID =
0;
alSourceUnqueueBuffers(m_source,
1, &bufferID);
SoundCallback(bufferID);
processed--;
}
Play();
}
return 0;
}
int ALEngine::SoundCallback(ALuint& bufferID)
{
PTFRAME frame = m_decoder->GetFrame();
if (frame ==
nullptr)
return -
1;
ALenum fmt;
if (frame->chs ==
1)
{
fmt = AL_FORMAT_MONO16;
}
else
{
fmt = AL_FORMAT_STEREO16;
}
alBufferData(bufferID, fmt, frame->data, frame->size, frame->samplerate);
alSourceQueueBuffers(m_source,
1, &bufferID);
if (frame)
{
av_free(frame->data);
delete frame;
}
return 0;
}
Test main.cpp
int main()
{
auto pe =
std::make_unique<ALEngine>();
pe->
OpenFile(
"D:\\music\\style.mp3");
std::this_thread::sleep_for(
std::chrono::milliseconds(
1000));
//pe->m_decoder->
SetSeekPosition(
0.
5);
while (
true)
{
std::this_thread::sleep_for(
std::chrono::milliseconds(
4000));
}
return 0;
}