播放步骤:
一个音频文件播放的过程包括以下几个阶段:
a) 解协议 —— 当音频文件在远端时,需要通过网格协议的方式传输到本地,如HTTP、RTSP、RTMP,这里会执行一个解协议的过程,去除协议头数据。
b) 解封装 —— 通常流媒体格式还包含媒体头,它对媒体中包含的流信息、流格式等元数据进行了描述,指导相关的逻辑进行媒体数据的进一步拆解。
c) 解码 —— 通常媒体数据都是编码过的,这里的编码主要是压缩,经压缩后的数据大小可以减小到原来的1/10或更多。
d) 重采样 —— 由于媒体数据的采样率、样本格式、通道布局等参数可能与播放单元要求的不一致,需要进行相应的数据转换,称之为重采样。
e) 播放 —— 经重采样后的PCM数据,按播放单元如ALSA要求的规范,调用相应的接口进行播放。
各步封装:
因为上面的流程比较多,后面演示例子的时候,如果将所有代码以过程式平铺在main函数里,那简直是没法看了。所以我们还是适当的按步骤进行封装,后面main函数将好看很多。
解封装:
// 错误信息维护
class CAVError
{
int m_nErrCode;
std::string m_strError;
public:
CAVError()
{
setError(0, "");
}
virtual ~CAVError()
{
}
// 设置错误信息
void setError(int nCode, const std::string& strError)
{
m_nErrCode = nCode;
if (m_nErrCode != 0)
{
char sErr[128] = {0};
av_strerror(m_nErrCode, sErr, sizeof(sErr));
m_strError = sErr;
}
else
{
m_strError = strError;
}
}
// 获取错误信息
std::string getError(int& nErrCode) const
{
char sTmp[32] = {0};
snprintf(sTmp, sizeof(sTmp), "%d", m_nErrCode);
nErrCode = m_nErrCode;
return std::string(sTmp) + " - " + m_strError;
}
};
// 输入文件封装器
class CAVInputWrapper
: public CAVError
{
AVFormatContext* m_pInputCTX;
public:
CAVInputWrapper()
: m_pInputCTX(NULL)
{
}
virtual ~CAVInputWrapper()
{
close();
}
// 打开指定文件
bool openFile(const std::string& strFile)
{
int rc = avformat_open_input(&m_pInputCTX, strFile.c_str(), NULL, NULL);
if (rc < 0)
{
setError(rc, "");
return false;
}
rc = avformat_find_stream_info(m_pInputCTX, NULL);
if (rc < 0)
{
avformat_close_input(&m_pInputCTX);
setError(rc, "");
return false;
}
return true;
}
// 关闭文件
void close()
{
if (m_pInputCTX)
{
avformat_close_input(&m_pInputCTX);
setError(0, "");
}
}
// 读取流信息
bool getStreamsInfo(std::vector<AVStream*>& vecPStream)
{
if (m_pInputCTX == NULL)
{
setError(0, "not open!");
return false;
}
for (int i = 0; i < m_pInputCTX->nb_streams; ++i)
{
vecPStream.push_back(m_pInputCTX->streams[i]);
}
return true;
}
// 读取一帧
bool getPacket(AVPacket* pPacket)
{
if (m_pInputCTX == NULL)
{
setError(0, "not open!");
return false;
}
int rc = av_read_frame(m_pInputCTX, pPacket);
if (rc < 0)
{
setError(rc, "");
return false;
}
return true;
}
};
解码:
// 解码封装器
class CAVDecodeWrapper
: public CAVError
{
const AVCodec* m_pCodec;
AVCodecContext* m_pCodecCTX;
public:
CAVDecodeWrapper()
: m_pCodec(NULL)
, m_pCodecCTX(NULL)
{
}
virtual ~CAVDecodeWrapper()
{
close();
}
// 打开解码器
bool openCodecByID(enum AVCodecID emCodecID, const AVCodecParameters* pCodecPar)
{
// 查找解码器
m_pCodec = avcodec_find_decoder(emCodecID);
if (m_pCodec == NULL)
{
setError(0, "can't find the codec!");
return false;
}
// 根据解码器分配上下文
m_pCodecCTX = avcodec_alloc_context3(m_pCodec);
if (m_pCodecCTX == NULL)
{
setError(0, "can't alloc the codec context!");
m_pCodec = NULL;
return false;
}
// 拷贝流中的编码器信息到上下文
if (pCodecPar)
{
int rc = avcodec_parameters_to_context(m_pCodecCTX, pCodecPar);
if (rc < 0)
{
setError(rc, "");
avcodec_free_context(&m_pCodecCTX);
m_pCodec = NULL;
return false;
}
}
// 打开解码器
int rc = avcodec_open2(m_pCodecCTX, m_pCodec, NULL);
if (rc < 0)
{
setError(rc, "");
avcodec_free_context(&m_pCodecCTX);
m_pCodec = NULL;
return false;
}
return true;
}
// 关闭解码器
void close()
{
if (m_pCodecCTX)
{
avcodec_free_context(&m_pCodecCTX);
}
if (m_pCodec)
{
m_pCodec = NULL;
}
}
// 发送编码数据
bool sendPacket(const AVPacket* pPacket)
{
if (m_pCodecCTX == NULL)
{
setError(0, "not open!");
return false;
}
int rc = avcodec_send_packet(m_pCodecCTX, pPacket);
if (rc < 0)
{
setError(rc, "");
return false;
}
return true;
}
// 接收一帧解码数据
bool recvFrame(AVFrame* pFrame)
{
if (m_pCodecCTX == NULL)
{
setError(0, "not open!");
return false;
}
int rc = avcodec_receive_frame(m_pCodecCTX, pFrame);
if (rc < 0)
{
setError(rc, "");
return false;
}
return true;
}
};
重采样:
// 音频重采样封装器
class CSWRWrapper
: public CAVError
{
struct SwrContext* m_pSwrCTX;
public:
CSWRWrapper()
: m_pSwrCTX(NULL)
{
}
virtual ~CSWRWrapper()
{
free();
}
struct SwrContext* handle()
{
return m_pSwrCTX;
}
// 初使化转换参数
bool init(int64_t out_ch_layout, enum AVSampleFormat out_sample_fmt, int out_sample_rate,
int64_t in_ch_layout, enum AVSampleFormat in_sample_fmt, int in_sample_rate)
{
// 分配重采样上下文,初使化参数
m_pSwrCTX = swr_alloc_set_opts(NULL, out_ch_layout, out_sample_fmt, out_sample_rate, in_ch_layout, in_sample_fmt, in_sample_rate, 0, NULL);
if (m_pSwrCTX == NULL)
{
setError(0, "swr_alloc_set_opts() failed!");
return false;
}
// 初使化上下文
int rc = swr_init(m_pSwrCTX);
if (rc < 0)
{
swr_free(&m_pSwrCTX);
setError(rc, "");
return false;
}
return true;
}
// 释放资源
void free()
{
if (m_pSwrCTX)
{
swr_free(&m_pSwrCTX);
}
}
// 重采样转换
int convert(uint8_t **out, int out_count, const uint8_t **in , int in_count)
{
if (m_pSwrCTX == NULL)
{
setError(0, "not init!");
return -1;
}
int rc = swr_convert(m_pSwrCTX, out, out_count, in, in_count);
if (rc < 0)
{
setError(rc, "");
return -1;
}
return rc;
}
};
ALSA播放:
ALSA是linux系统的音频播放架构,在windows下就不行了,windows请采用其他合适的方案吧,或者采用SDL库统一跨平台。
#if defined(__linux__)
#include <alsa/asoundlib.h>
// 错误信息维护
class CALSAError
{
int m_nErrCode;
std::string m_strError;
public:
CALSAError()
{
setError(0, "");
}
virtual ~CALSAError()
{
}
// 设置错误信息
void setError(int nCode, const std::string& strError)
{
m_nErrCode = nCode;
if (m_nErrCode != 0)
{
m_strError = snd_strerror(m_nErrCode);
}
else
{
m_strError = strError;
}
}
// 获取错误信息
std::string getError(int& nErrCode) const
{
char sTmp[32] = {0};
snprintf(sTmp, sizeof(sTmp), "%d", m_nErrCode);
nErrCode = m_nErrCode;
return std::string(sTmp) + " - " + m_strError;
}
};
// ALSA声卡封装器
class CALSAWrapper
: public CALSAError
{
snd_pcm_t* m_pPCM;
// 声卡硬件参数
snd_pcm_access_t m_xAccess;
snd_pcm_format_t m_xSampleFmt;
unsigned int m_nChannels;
unsigned int m_nSampleRate;
snd_pcm_uframes_t m_xPeriodFrames;
public:
CALSAWrapper()
: m_pPCM(NULL)
, m_xAccess(SND_PCM_ACCESS_RW_INTERLEAVED)
, m_xSampleFmt(SND_PCM_FORMAT_S16_LE)
, m_nChannels(2)
, m_nSampleRate(44100)
, m_xPeriodFrames(1024)
{
}
virtual ~CALSAWrapper()
{
close();
}
// 声卡硬件参数访问
void setAccess(snd_pcm_access_t xAccess)
{
m_xAccess = xAccess;
}
snd_pcm_access_t getAccess()
{
return m_xAccess;
}
void setSampleFormat(snd_pcm_format_t xSampleFmt)
{
m_xSampleFmt = xSampleFmt;
}
snd_pcm_format_t getSampleFormat()
{
return m_xSampleFmt;
}
void setChannels(unsigned int nChannels)
{
m_nChannels = nChannels;
}
unsigned int getChannels()
{
return m_nChannels;
}
void setSampleRate(unsigned int nSampleRate)
{
m_nSampleRate = nSampleRate;
}
unsigned int getSampleRate()
{
return m_nSampleRate;
}
void setPeriodFrames(snd_pcm_uframes_t xPeriodFrames)
{
m_xPeriodFrames = xPeriodFrames;
}
snd_pcm_uframes_t getPeriodFrames()
{
return m_xPeriodFrames;
}
// 打开回放
bool openPlayBack()
{
// 打开默认设备
int rc = snd_pcm_open(&m_pPCM, "default", SND_PCM_STREAM_PLAYBACK, 0);
if (rc < 0)
{
setError(rc, "");
return false;
}
// 分配硬件参数,填充默认值
snd_pcm_hw_params_t *pHWParams = NULL;
snd_pcm_hw_params_malloc(&pHWParams);
snd_pcm_hw_params_any(m_pPCM, pHWParams);
do
{
// 设置硬件参数
rc = snd_pcm_hw_params_set_access(m_pPCM, pHWParams, m_xAccess);
if (rc < 0)
break;
rc = snd_pcm_hw_params_set_format(m_pPCM, pHWParams, m_xSampleFmt);
if (rc < 0)
break;
rc = snd_pcm_hw_params_set_channels(m_pPCM, pHWParams, m_nChannels);
if (rc < 0)
break;
rc = snd_pcm_hw_params_set_rate_near(m_pPCM, pHWParams, &m_nSampleRate, NULL);
if (rc < 0)
break;
rc = snd_pcm_hw_params_set_period_size_near(m_pPCM, pHWParams, &m_xPeriodFrames, NULL);
if (rc < 0)
break;
rc = snd_pcm_hw_params(m_pPCM, pHWParams);
if (rc < 0)
break;
// 释放硬件参数内存
snd_pcm_hw_params_free(pHWParams);
pHWParams = NULL;
return true;
} while (0);
// 打开失败,执行清理工作
if (pHWParams)
{
snd_pcm_hw_params_free(pHWParams);
pHWParams = NULL;
}
snd_pcm_hw_free(m_pPCM);
m_pPCM = NULL;
setError(rc, "");
return false;
}
// 关闭声卡,清理资源
void close()
{
if (m_pPCM)
{
snd_pcm_drain(m_pPCM);
snd_pcm_close(m_pPCM);
snd_pcm_hw_free(m_pPCM);
m_pPCM = NULL;
}
}
// 向声卡写入一段PCM缓冲
snd_pcm_sframes_t writePCM(const void *pBuffer, snd_pcm_uframes_t uSize)
{
if (m_pPCM == NULL)
{
setError(0, "not open!");
return -1;
}
snd_pcm_sframes_t ret = snd_pcm_writei(m_pPCM, pBuffer, uSize);
if (ret == -EPIPE)
{
snd_pcm_prepare(m_pPCM);
ret = snd_pcm_writei(m_pPCM, pBuffer, uSize);
}
if (ret < 0)
{
setError(ret, "");
return -1;
}
if (ret > 0 && (snd_pcm_uframes_t)ret < uSize)
{
setError(0, "short write!");
}
return ret;
}
};
#endif
PCM缓冲:
需要注意的是,很多播放单元如ALSA,要求上层提供给播放单元的样本个数是有严格要求的,否则将不能很好的利用播放单元的内部缓冲,造成播放时的噪音或者沙沙声。解决办法就是,我们可以设置一个PCM缓冲,将解码或者重采样后的音频数据先排队在缓冲里,然后再需要的块大小交给播放单元。
// PCM缓冲
class CPCMBuffer
{
char* m_pData;
int m_nSize;
int m_nCapacity;
public:
CPCMBuffer()
: m_pData(NULL)
, m_nSize(0)
, m_nCapacity(0)
{
}
virtual ~CPCMBuffer()
{
__clear();
}
// 添加数据
void append(const char* pData, int nSize)
{
__tryIncrement(nSize);
__append(pData, nSize);
}
// 直接添加AVFrame
void append(AVFrame* pFrame)
{
// 计算音频数据的大小
int nSampleBytes = av_get_bytes_per_sample((AVSampleFormat)pFrame->format);
int nPCMSize = nSampleBytes * pFrame->channels * pFrame->nb_samples;
char* pPCMBuffer = new char[nPCMSize];
int nOffset = 0;
for (int i = 0; i < pFrame->nb_samples; ++i)
{
for (int c = 0; c < pFrame->channels; ++c)
{
memcpy(pPCMBuffer + nOffset, pFrame->data[c] + nSampleBytes * i, nSampleBytes);
nOffset += nSampleBytes;
}
}
append(pPCMBuffer, nPCMSize);
delete[] pPCMBuffer;
}
// 提供外部缓冲区,获取指定大小的数据
bool fetch(char* pData, int nSize)
{
return __fetch(pData, nSize);
}
// 不提供外部缓冲区,获取指定大小的数据
bool fetch(char** ppData, int nSize)
{
char* pData = new char[nSize];
if (!__fetch(pData, nSize))
{
delete[] pData;
return false;
}
*ppData = pData;
return true;
}
// 释放之前fetch()的内存
void free(char* pData)
{
delete[] pData;
}
private:
void __clear()
{
if (m_pData)
{
delete[] m_pData;
m_pData = NULL;
m_nSize = 0;
m_nCapacity = 0;
}
}
void __tryIncrement(int nSize)
{
if (m_nCapacity - m_nSize >= nSize)
return;
int nAdd = nSize - (m_nCapacity - m_nSize);
int nNewCapacity = m_nCapacity + nAdd;
char* pNewData = new char[nNewCapacity];
memcpy(pNewData, m_pData, m_nSize);
delete[] m_pData;
m_pData = pNewData;
m_nCapacity = nNewCapacity;
}
void __append(const char* pData, int nSize)
{
memcpy(m_pData + m_nSize, pData, nSize);
m_nSize += nSize;
}
bool __fetch(char* pData, int nSize)
{
if (m_nSize < nSize)
return false;
memcpy(pData, m_pData, nSize);
memcpy(m_pData, m_pData + nSize, m_nSize - nSize);
m_nSize -= nSize;
return true;
}
};
执行调用:
请将上面的代码收集到一起,然后编写下面的main()调用:
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <string>
#include <vector>
extern "C"
{
#include <libavformat/avformat.h>
#include <libavcodec/avcodec.h>
#include <libswresample/swresample.h>
}
int main(int argc, char* argv[])
{
CAVInputWrapper avInput;
// 打开媒体文件
bool bRet = avInput.openFile("1.flv");
if (!bRet)
{
int nErrCode = 0;
printf("open file failed, err:[%s] \n", avInput.getError(nErrCode).c_str());
return -1;
}
// 读取流信息
std::vector<AVStream*> vecPStream;
avInput.getStreamsInfo(vecPStream);
// 选择第一路音频流
int nAudioIndex = -1;
for (std::vector<AVStream*>::iterator iter = vecPStream.begin(); iter != vecPStream.end(); ++iter)
{
if ((*iter)->codecpar->codec_type == AVMEDIA_TYPE_AUDIO)
{
nAudioIndex = (*iter)->index;
}
}
if (nAudioIndex < 0)
{
printf("no audio stream! \n");
return -1;
}
// 打印所选音频的格式信息
AVCodecParameters* pCodecPar = vecPStream[nAudioIndex]->codecpar;
printf("codec:[%d - %s] \n", pCodecPar->codec_id, avcodec_get_name(pCodecPar->codec_id));
printf("format:[%d - %s] \n", pCodecPar->format, av_get_sample_fmt_name((AVSampleFormat)pCodecPar->format));
printf("channel_layout:[%lld - %s] \n", pCodecPar->channel_layout, av_get_channel_name(pCodecPar->channel_layout));
printf("channels:[%d] \n", pCodecPar->channels);
printf("sample_rate:[%d] \n", pCodecPar->sample_rate);
// 计划向ALSA输出AV_SAMPLE_FMT_S16格式
bool bRequireSWR = false;
if (pCodecPar->format != AV_SAMPLE_FMT_S16)
{
bRequireSWR = true;
printf("stream format not [%d - %s], must resample! \n", AV_SAMPLE_FMT_S16, av_get_sample_fmt_name((AVSampleFormat)AV_SAMPLE_FMT_S16));
}
// 打开解码器
CAVDecodeWrapper avDecodec;
if (!avDecodec.openCodecByID(vecPStream[nAudioIndex]->codecpar->codec_id, vecPStream[nAudioIndex]->codecpar))
{
int nErrCode = 0;
printf("open decodec failed, err:[%s] \n", avDecodec.getError(nErrCode).c_str());
return -1;
}
unsigned int nRealSampleRate = pCodecPar->sample_rate;
int xRealPeriodFrames = 1024;
#if defined(__linux__)
CALSAWrapper alsa;
// 设置ALSA相关参数
alsa.setAccess(SND_PCM_ACCESS_RW_INTERLEAVED);
alsa.setSampleFormat(SND_PCM_FORMAT_S16_LE);
alsa.setChannels(pCodecPar->channels);
alsa.setSampleRate(pCodecPar->sample_rate);
alsa.setPeriodFrames(1024);
// 打开ALSA声卡
if (!alsa.openPlayBack())
{
int nErrCode = 0;
printf("open alsa failed, err:[%s] \n", alsa.getError(nErrCode).c_str());
return -1;
}
// 获取ALSA实际采纳的采样率和周期帧数
nRealSampleRate = alsa.getSampleRate();
xRealPeriodFrames = alsa.getPeriodFrames();
// 检查ALSA实际接受的采样率是否与媒体不一致,则需要重采样
if (nRealSampleRate != pCodecPar->sample_rate)
{
bRequireSWR = true;
printf("stream sample rate:[%d], but alsa support is:[%d], must resample! \n", pCodecPar->sample_rate, nRealSampleRate);
}
#endif
CSWRWrapper swr;
// 初使化音频重采样
if (bRequireSWR
&& !swr.init(pCodecPar->channel_layout, AV_SAMPLE_FMT_S16, nRealSampleRate,
pCodecPar->channel_layout, (enum AVSampleFormat)pCodecPar->format, pCodecPar->sample_rate) )
{
int nErrCode = 0;
printf("SWR init failed, err:[%s] \n", swr.getError(nErrCode).c_str());
return -1;
}
// PCM缓冲
CPCMBuffer pcmBuffer;
AVPacket* pPacket = av_packet_alloc();
AVFrame* pFrame = av_frame_alloc();
// 读取媒体帧
while (true)
{
if (!avInput.getPacket(pPacket))
{
int nErrCode = 0;
std::string strError = avInput.getError(nErrCode);
if (nErrCode != AVERROR_EOF)
{
printf("read frame failed, err:[%s] \n", strError.c_str());
}
break;
}
// 只关注选择的音频流
if (pPacket->stream_index != nAudioIndex)
continue;
printf("packet pts:[%lld] dts:[%lld] duration:[%lld] size:[%d] \n", pPacket->pts, pPacket->dts, pPacket->duration, pPacket->size);
// 发送编码数据
avDecodec.sendPacket(pPacket);
// 持续接收解码数据
while (true)
{
if (!avDecodec.recvFrame(pFrame))
{
int nErrCode = 0;
std::string strError = avDecodec.getError(nErrCode);
if (nErrCode != AVERROR(EAGAIN) && nErrCode != AVERROR_EOF)
{
printf("read frame failed, err:[%s] \n", strError.c_str());
}
break;
}
printf("frame format:[%d:%s] channels:[%d] sample_rate:[%d] nb_samples:[%d] pkt_size:[%d] linesize:[%d] \n",
pFrame->format, av_get_sample_fmt_name((AVSampleFormat)pFrame->format), pFrame->channels, pFrame->sample_rate, pFrame->nb_samples, pFrame->pkt_size, pFrame->linesize[0]);
// 需要音频重采样
if (bRequireSWR)
{
// 计算重采样样本数
int dst_nb_samples = av_rescale_rnd(swr_get_delay(swr.handle(), pCodecPar->sample_rate) +
pFrame->nb_samples, nRealSampleRate, pCodecPar->sample_rate, AV_ROUND_UP);
// 分配重采样空间
uint8_t **pDstData = NULL;
av_samples_alloc_array_and_samples(&pDstData, NULL, pFrame->channels, dst_nb_samples, AV_SAMPLE_FMT_S16, 0);
// 重采样转换
int nConvertSamples = 0;
if ((nConvertSamples = swr.convert(pDstData, dst_nb_samples, (const uint8_t**)pFrame->data, pFrame->nb_samples)) > 0)
{
int nConvertSize = av_samples_get_buffer_size(NULL, pFrame->channels, nConvertSamples, AV_SAMPLE_FMT_S16, 1);
// 添加重采样数据到PCM缓冲
pcmBuffer.append((const char*)pDstData[0], nConvertSize);
}
else
{
int nErrCode = 0;
printf("SWR convert failed, err:[%s] \n", swr.getError(nErrCode).c_str());
}
// 释放重采样空间
if (pDstData)
{
av_freep(&pDstData[0]);
}
av_freep(&pDstData);
}
else
{
// 添加原始数据到PCM缓冲
pcmBuffer.append(pFrame);
}
// 从PCM缓冲取周期数据
int nSampleBytes = av_get_bytes_per_sample(AV_SAMPLE_FMT_S16);
int nPeriodBytes = nSampleBytes * pFrame->channels * xRealPeriodFrames;
char* pPeriodBuffer = NULL;
while (pcmBuffer.fetch(&pPeriodBuffer, nPeriodBytes))
{
#if defined(__linux__)
// 交给ALSA声卡播放
int nWriteFrames = alsa.writePCM(pPeriodBuffer, xRealPeriodFrames);
printf("pcm write:[%d] ret:[%d] \n", xRealPeriodFrames, nWriteFrames);
#endif
pcmBuffer.free(pPeriodBuffer);
}
}
// 释放帧引用的内存
av_packet_unref(pPacket);
}
// 刷新解码缓冲区
avDecodec.sendPacket(NULL);
// 接收尾包
if (avDecodec.recvFrame(pFrame))
{
printf("tail frame format:[%d:%s] channels:[%d] sample_rate:[%d] nb_samples:[%d] pkt_size:[%d] linesize:[%d] \n",
pFrame->format, av_get_sample_fmt_name((AVSampleFormat)pFrame->format), pFrame->channels, pFrame->sample_rate, pFrame->nb_samples, pFrame->pkt_size, pFrame->linesize[0]);
// 简单起见,忽略尾包处理
}
av_packet_free(&pPacket);
av_frame_free(&pFrame);
swr.free();
#if defined(__linux__)
alsa.close();
#endif
// 关闭解码器
avDecodec.close();
// 关闭媒体文件
avInput.close();
return 0;
}
编译,只能在linux上进行了:
g++ -o testaudio testaudio.cpp -I/usr/local/ffmpeg/include -L/usr/local/ffmpeg/lib -lavformat -lavcodec -lavutil -lswscale -lswresample -lva -lasound -pthread -lz -lmp3lame -lX11 -lva-drm -lva-x11 -lm -ggdb
执行前,代码要求同目标下有1.flv的文件,当然你可以改成其他的媒体文件,理论上应该各种格式都支持,输出:
codec:[86017 - mp3]
format:[8 - fltp]
channel_layout:[4 - FC]
channels:[1]
sample_rate:[44100]
stream format not [1 - s16], must resample!
packet pts:[0] dts:[0] duration:[26] size:[208]
frame format:[8:fltp] channels:[1] sample_rate:[44100] nb_samples:[1152] pkt_size:[208] linesize:[4608]
pcm write:[940] ret:[940]
packet pts:[26] dts:[26] duration:[26] size:[209]
frame format:[8:fltp] channels:[1] sample_rate:[44100] nb_samples:[1152] pkt_size:[209] linesize:[4608]
pcm write:[940] ret:[940]
packet pts:[52] dts:[52] duration:[26] size:[209]
frame format:[8:fltp] channels:[1] sample_rate:[44100] nb_samples:[1152] pkt_size:[209] linesize:[4608]
pcm write:[940] ret:[940]
packet pts:[78] dts:[78] duration:[26] size:[209]
frame format:[8:fltp] channels:[1] sample_rate:[44100] nb_samples:[1152] pkt_size:[209] linesize:[4608]
pcm write:[940] ret:[940]
packet pts:[104] dts:[104] duration:[26] size:[209]
frame format:[8:fltp] channels:[1] sample_rate:[44100] nb_samples:[1152] pkt_size:[209] linesize:[4608]
pcm write:[940] ret:[940]
pcm write:[940] ret:[940]
packet pts:[131] dts:[131] duration:[26] size:[209]
frame format:[8:fltp] channels:[1] sample_rate:[44100] nb_samples:[1152] pkt_size:[209] linesize:[4608]
pcm write:[940] ret:[940]
packet pts:[157] dts:[157] duration:[26] size:[209]
frame format:[8:fltp] channels:[1] sample_rate:[44100] nb_samples:[1152] pkt_size:[209] linesize:[4608]
pcm write:[940] ret:[940]
packet pts:[183] dts:[183] duration:[26] size:[209]
frame format:[8:fltp] channels:[1] sample_rate:[44100] nb_samples:[1152] pkt_size:[209] linesize:[4608]
pcm write:[940] ret:[940]
packet pts:[209] dts:[209] duration:[26] size:[209]
frame format:[8:fltp] channels:[1] sample_rate:[44100] nb_samples:[1152] pkt_size:[209] linesize:[4608]
pcm write:[940] ret:[940]
pcm write:[940] ret:[940]
......
留意下喇叭输出。