第五节 C++队列缓存AVPacket、生成PCM数据

因为解码获取AVpakcet需要耗费一定的时间,为了达到更好地播放效果(流畅度),需要把解码出来的AVpacket先缓存到队列中,播放时直接从队里里面取。


第五节 C++队列缓存AVPacket、生成PCM数据_第1张图片
image.png

出队和入队的时候都需要加锁控制,消费者、生产者模式

再封装两个类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文件:


image.png

如果要测试生成的pcm数据是否可以正常播放的话,可以利用windows系统的voiceplayer软件
文件-导入-原始数据


第五节 C++队列缓存AVPacket、生成PCM数据_第2张图片
image.png

你可能感兴趣的:(第五节 C++队列缓存AVPacket、生成PCM数据)