因为解码获取AVpakcet需要耗费一定的时间,为了达到更好地播放效果(流畅度),需要把解码出来的AVpacket先缓存到队列中,播放时直接从队里里面取。
出队和入队的时候都需要加锁控制,消费者、生产者模式
再封装两个类HQueue和HPlayStatus
HPlayStatus描述的是播放的状态,HQueue封装了出队和入队的具体操作
了解几个概念:
参考文章:https://blog.csdn.net/xiaojun111111/article/details/41909395
声音是能量值,是按照波形表示的,也就是模拟信号
采样率:(一秒采多少次样本)
采样大小:(能表示的值,也就是振动的幅度)
频率:是一秒有多少个波形
采样率和频率的关系:一个波形中可以被采几次样本
注意:对于原始数据提高采样率可以提高声音的质量,但是对于数字信号,不会提高
channel_layout:声道设计,是指的单声道,双声道,立体声
channel_layout和channels(声道数)的关系,可以通过函数channel_layout根据select_channel_layout(codec)来选择,channels通过av_get_channel_layout_nb_channels(channel_layout)来设定
HPlayStatus.h
//
// Created by 霍振鹏 on 2018/10/19.
//
#ifndef VOICEPLAYER_HPLAYSTATUS_H
#define VOICEPLAYER_HPLAYSTATUS_H
class HPlayStatus
{
public:
bool exit;
public:
HPlayStatus();
~HPlayStatus();
};
#endif //VOICEPLAYER_HPLAYSTATUS_H
HPlayStatus.cpp
//
// Created by 霍振鹏 on 2018/10/19.
//
#include "HPlayStatus.h"
HPlayStatus::HPlayStatus() {
this->exit= false;
}
HPlayStatus::~HPlayStatus() {
}
HQueue.h
//
// Created by 霍振鹏 on 2018/10/19.
//
#ifndef VOICEPLAYER_HQUEUE_H
#define VOICEPLAYER_HQUEUE_H
#include
#include
#include "HPlayStatus.h"
#include "Log.h"
extern "C"
{
#include
};
class HQueue
{
public:
HQueue(HPlayStatus *playStatus);
~HQueue();
void putAvPacket(AVPacket *avPacket);
int getAvPacket(AVPacket *avPacket);
int getQueueSize();
public:
pthread_mutex_t pthread_mutex;
pthread_cond_t pthread_cond;
//创建了一个队列,已经创建了
std::queue queue;
HPlayStatus *hPlayStatus=NULL;
};
#endif //VOICEPLAYER_HQUEUE_H
HQueue.cpp
//
// Created by 霍振鹏 on 2018/10/19.
//
#include "HQueue.h"
HQueue::~HQueue() {
}
HQueue::HQueue(HPlayStatus *hPlayStatus) {
this->hPlayStatus=hPlayStatus;
pthread_mutex_init(&pthread_mutex,NULL);
pthread_cond_init(&pthread_cond,NULL);
}
void HQueue::putAvPacket(AVPacket *avPacket) {
pthread_mutex_lock(&pthread_mutex);
queue.push(avPacket);
if(LOG_DEBUG)
{
//LOGI("队列中的个数:%d",queue.size());
}
pthread_cond_signal(&pthread_cond);
pthread_mutex_unlock(&pthread_mutex);
}
int HQueue::getAvPacket(AVPacket *avPacket) {
pthread_mutex_lock(&pthread_mutex);
while (hPlayStatus!=NULL&&!hPlayStatus->exit)
{
if(queue.size()>0)
{
AVPacket *packet=queue.front();
//return 0 on success
//Setup a new reference to the data described by a given packet
if(av_packet_ref(avPacket,packet)==0)
{
queue.pop();
}
av_packet_free(&packet);
av_free(packet);
packet=NULL;
if(LOG_DEBUG)
{
LOGI("从队列中取出一个AVPacket,还剩余%d个",queue.size());
}
break;
} else{
pthread_cond_wait(&pthread_cond,&pthread_mutex);
}
}
pthread_mutex_unlock(&pthread_mutex);
return 0;
}
int HQueue::getQueueSize() {
int size=0;
pthread_mutex_lock(&pthread_mutex);
size=queue.size();
pthread_mutex_unlock(&pthread_mutex);
return size;
}
HAudio.h
//
// Created by 霍振鹏 on 2018/10/19.
//
#ifndef VOICEPLAYER_HAUDIO_H
#define VOICEPLAYER_HAUDIO_H
#include "HQueue.h"
extern "C"
{
#include "libavcodec/avcodec.h"
};
class HAudio{
public:
int streamIndex=-1;
AVCodecParameters *avCodecParameters=NULL;
AVCodecContext *avCodecContext=NULL;
HQueue *hQueue=NULL;
HPlayStatus *hPlayStatus=NULL;
pthread_t pthread;
uint8_t *buffer=NULL;
public:
HAudio(HPlayStatus *hPlayStatus);
~HAudio();
void play();
int resampleAudio();
};
#endif //VOICEPLAYER_HAUDIO_H
HAudio.cpp
//
// Created by 霍振鹏 on 2018/10/19.
//
#include "HAudio.h"
extern "C"
{
#include
};
HAudio::HAudio(HPlayStatus *hPlayStatus) {
this->hPlayStatus=hPlayStatus;
this->hQueue=new HQueue(hPlayStatus);
buffer = (uint8_t *) av_malloc(44100 * 2 * 2);
}
HAudio::~HAudio() {
}
void *writeData(void *data)
{
HAudio *hAudio= (HAudio *) data;
hAudio->resampleAudio();
pthread_exit(&hAudio->pthread);
}
void HAudio::play() {
//创建一个线程,准备写入pcm数据
pthread_create(&pthread,NULL,writeData,this);
}
FILE *outFile=fopen("/storage/emulated/0/voiceplayer.pcm","w");
int HAudio::resampleAudio() {
if(LOG_DEBUG)
{
LOGI("开始写入pcm数据");
}
while(hPlayStatus!=NULL&&!hPlayStatus->exit)
{
if(LOG_DEBUG)
{
LOGI("进入循环");
}
AVPacket *avPacket=av_packet_alloc();
if(hQueue->getAvPacket(avPacket)!=0)
{
av_packet_free(&avPacket);
av_free(avPacket);
avPacket=NULL;
continue;
}
//return 0 on success
int ret=0;
ret=avcodec_send_packet(avCodecContext,avPacket);
if(ret!=0)
{
av_packet_free(&avPacket);
av_free(avPacket);
avPacket=NULL;
continue;
}
AVFrame *avFrame=av_frame_alloc();
ret =avcodec_receive_frame(avCodecContext,avFrame);
if(ret==0)
{
if(avFrame->channels>0&&avFrame->channel_layout==0)
{
avFrame->channel_layout=av_get_default_channel_layout(avFrame->channels);
}
else if(avFrame->channels==0&&avFrame->channel_layout>0)
{
avFrame->channel_layout=av_get_channel_layout_nb_channels(avFrame->channel_layout);
}
SwrContext *swrContext;
swrContext=swr_alloc_set_opts(
NULL,
AV_CH_LAYOUT_STEREO,
AV_SAMPLE_FMT_S16,
avFrame->sample_rate,
avFrame->channel_layout,
(AVSampleFormat) avFrame->format,
avFrame->sample_rate,
NULL,NULL
);
if(!swrContext||swr_init(swrContext)<0)
{
av_packet_free(&avPacket);
av_free(avPacket);
avPacket = NULL;
av_frame_free(&avFrame);
av_free(avFrame);
avFrame = NULL;
swr_free(&swrContext);
continue;
}
//return number of samples output per channel
int nb=swr_convert(
swrContext,
&buffer,
avFrame->nb_samples,
(const uint8_t **) avFrame->data,
avFrame->nb_samples
);
int out_channels=av_get_channel_layout_nb_channels(AV_CH_LAYOUT_STEREO);
int data_size=nb*out_channels*av_get_bytes_per_sample(AV_SAMPLE_FMT_S16);
fwrite(buffer,1,data_size,outFile);
LOGI("data_size is %d", data_size);
av_packet_free(&avPacket);
av_free(avPacket);
avPacket = NULL;
av_frame_free(&avFrame);
av_free(avFrame);
avFrame = NULL;
swr_free(&swrContext);
} else{
av_packet_free(&avPacket);
av_free(avPacket);
avPacket=NULL;
av_frame_free(&avFrame);
av_free(avFrame);
avFrame=NULL;
continue;
}
}
fclose(outFile);
return 0;
}
HFFmpeg.h
//
// Created by 霍振鹏 on 2018/10/19.
//
#include "CallBackJava.h"
#include
// sleep 的头文件
#include
#include "Log.h"
#include "HAudio.h"
#include "HPlayStatus.h"
extern "C"
{
#include
}
#ifndef VOICEPLAYER_HFFMPEG_H
#define VOICEPLAYER_HFFMPEG_H
#endif //VOICEPLAYER_HFFMPEG_H
class HFFmpeg{
public:
//记得全部置为NULL
//需要解码的音频文件的地址
const char* url=NULL;
//一般可能都需要回调Java层代码
CallBackJava *callBackJava=NULL;
//解码线程
pthread_t pthread_decode=NULL;
AVFormatContext *avFormatContext=NULL;
HAudio *hAudio=NULL;
HPlayStatus *hPlayStatus=NULL;
public:
HFFmpeg(const char* url,CallBackJava *callBackJava,HPlayStatus *hPlayStatus);
/**
* 准备解码
*/
void prepare();
/**
* 开始解码
*/
void start();
/**
* 解码线程回调方法
*/
void decode();
~HFFmpeg();
};
HFFmpeg.cpp
//
// Created by 霍振鹏 on 2018/10/19.
//
#include "HFFmpeg.h"
#include "HAudio.h"
HFFmpeg::~HFFmpeg() {
}
HFFmpeg::HFFmpeg(const char *url, CallBackJava *callBackJava,HPlayStatus *hPlayStatus) {
this->url=url;
this->callBackJava=callBackJava;
this->hPlayStatus=hPlayStatus;
}
void * decodeFFmpeg(void * data)
{
HFFmpeg *hfFmpeg= (HFFmpeg *) data;
hfFmpeg->decode();
pthread_exit(&hfFmpeg->pthread_decode);
}
void HFFmpeg::prepare() {
//初始化线程,开始解码
pthread_create(&pthread_decode,NULL,decodeFFmpeg,this);
}
void HFFmpeg::start() {
if(hAudio==NULL)
{
LOGI("audio is NULL");
return;
}
//这儿开启新线程开始写入文件
hAudio->play();
int count=0;
while (hPlayStatus!=NULL&&!hPlayStatus->exit)
{
AVPacket *avPacket=av_packet_alloc();
//0 if OK
if(av_read_frame(avFormatContext,avPacket)==0)
{
if(avPacket->stream_index==hAudio->streamIndex)
{
if(LOG_DEBUG)
{
count++;
//LOGI("解码第%d帧",count);
}
hAudio->hQueue->putAvPacket(avPacket);
} else{
av_packet_free(&avPacket);
av_free(avPacket);
}
} else{
if(LOG_DEBUG)
{
LOGI("解码出现错误");
}
av_packet_free(&avPacket);
av_free(avPacket);
//清除队列中的缓存
while (hPlayStatus!=NULL&&!hPlayStatus->exit)
{
if(hAudio->hQueue->getQueueSize()>0)
{
continue;
} else{
hPlayStatus->exit=true;
break;
}
}
}
}
if(LOG_DEBUG)
{
LOGI("解码完成");
}
}
void HFFmpeg::decode() {
av_register_all();
avformat_network_init();
//打开本地文件或者网络流
avFormatContext=avformat_alloc_context();
//0 on success
int result=0;
result=avformat_open_input(&avFormatContext,url,NULL,NULL);
if(result!=0)
{
if(LOG_DEBUG)
{
LOGI("打开媒体文件失败");
}
return ;
}
//查找流信息return >=0 if OK
result=avformat_find_stream_info(avFormatContext,NULL);
if(result<0)
{
if(LOG_DEBUG)
{
LOGI("can not find streams from %s", url);
}
return ;
}
//查找音频流的索引
for(int i=0;inb_streams;i++)
{
if(avFormatContext->streams[i]->codecpar->codec_type==AVMEDIA_TYPE_AUDIO)
{
if(hAudio==NULL)
{
hAudio=new HAudio(hPlayStatus);
hAudio->streamIndex=i;
hAudio->avCodecParameters=avFormatContext->streams[i]->codecpar;
}
}
}
//获取音频解码器
AVCodec *avCodec=avcodec_find_decoder(hAudio->avCodecParameters->codec_id);
if(avCodec==NULL)
{
if(LOG_DEBUG)
{
LOGI("获取音频解码器失败")
}
return ;
}
//创建解码器上下文
hAudio->avCodecContext=avcodec_alloc_context3(avCodec);
if(hAudio->avCodecContext==NULL)
{
if(LOG_DEBUG)
{
LOGI("创建解码器上下文失败");
}
return ;
}
//将音频流信息拷贝到新的AVCodecContext结构体中 return >= 0 on success
if(avcodec_parameters_to_context(hAudio->avCodecContext,hAudio->avCodecParameters)<0)
{
if(LOG_DEBUG)
{
LOGI("拷贝失败");
}
return ;
}
//打开解码器 zero on success
if(avcodec_open2(hAudio->avCodecContext,avCodec,NULL)!=0)
{
if(LOG_DEBUG)
{
LOGI("打开解码器失败");
}
return ;
}
const char* msg="解码初始化完成";
callBackJava->onPrepared(0,msg);
}
Player.java
package com.example.voicelib;
import android.util.Log;
/**
* 作者 huozhenpeng
* 日期 2018/10/18
* 邮箱 [email protected]
*/
public class Player {
// Used to load the 'native-lib' library on application startup.
static {
System.loadLibrary("native-lib");
System.loadLibrary("avutil-55");
System.loadLibrary("swresample-2");
System.loadLibrary("avcodec-57");
System.loadLibrary("avformat-57");
System.loadLibrary("swscale-4");
System.loadLibrary("postproc-54");
System.loadLibrary("avfilter-6");
System.loadLibrary("avdevice-57");
}
public void onError(int code,String msg)
{
Log.e("VoicePlayer","code:"+code+"msg:"+msg);
}
public void onPrepared(String msg)
{
Log.e("VoicePlayer","[Java]msg:"+msg);
new Thread(new Runnable() {
@Override
public void run() {
start();
}
}).start();
}
/**
* 初始化解码
*/
public native void startDecode(String path);
/**
* 开始解码
*/
public native void start();
}
测试:
生成的pcm文件:
如果要测试生成的pcm数据是否可以正常播放的话,可以利用windows系统的voiceplayer软件
文件-导入-原始数据