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

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


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 <pthread.h>
#include <queue>
#include "HPlayStatus.h"
#include "Log.h"

extern "C"
{
#include <libavcodec/avcodec.h>
};


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<AVPacket *> 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 <libswresample/swresample.h>
};

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 <pthread.h>
// sleep 的头文件
#include <unistd.h>
#include "Log.h"
#include "HAudio.h"
#include "HPlayStatus.h"


extern "C"
{
#include <libavformat/avformat.h>
}

#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;i<avFormatContext->nb_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
 * 邮箱 huohacker@sina.com
 */

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软件
文件-导入-原始数据


image.png
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,029评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,238评论 3 388
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 159,576评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,214评论 1 287
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,324评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,392评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,416评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,196评论 0 269
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,631评论 1 306
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,919评论 2 328
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,090评论 1 342
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,767评论 4 337
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,410评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,090评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,328评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,952评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,979评论 2 351

推荐阅读更多精彩内容