音视频播放器设计(四)——ffmpeg音频处理流程

音频控制

开启音频

QAudioFormat:设置音频的格式相关信息,如采样率、采样位数、通道数等信息。

QAudioOutput:控制音频的播放、暂停、设置和获取音量等。

QIODevice:所有输入输出IO类的基础类,为IO类提供了统一的调用接口。

QAudioOutput* output = NULL;
QIODevice* io = NULL;
QMutex mutex;

bool Start()
{
    Stop();
    mutex.lock();
    QAudioOutput *out;               // 播放音频
    QAudioFormat fmt;                // 设置音频输出格式
    fmt.setSampleRate(48000);        // 1秒的音频采样率
    fmt.setSampleSize(16);           // 声音样本的大小
    fmt.setChannelCount(2);          // 声道
    fmt.setCodec("audio/pcm");       // 解码格式
    fmt.setByteOrder(QAudioFormat::LittleEndian);
    fmt.setSampleType(QAudioFormat::UnSignedInt);// 设置音频类型
    output = new QAudioOutput(fmt);
    io = output->start();            // 播放开始
    mutex.unlock();
    return true;
}

停止音频

void Stop()
{
    mutex.lock();
    if (output)
    {
        output->stop();
        delete output;
        output = NULL;
        io = NULL;
    }
    mutex.unlock();
}

音频播放

使用QAudioOutput的接口resume/suspend来恢复/继续播放音频。

void  Play(bool isplay)
{
    mutex.lock();
    if (!output)
    {
        mutex.unlock();
        return;
    }

    if (isplay)
    {
        output->resume();   // 恢复播放
    }
    else
    {
        output->suspend();  // 暂停播放
    }
    mutex.unlock();
}

音频写入缓冲

bool Write(const char *data, int datasize)
{
    mutex.lock();
    if (io)
        io->write(data, datasize);
    mutex.unlock();
    return true;
}

音频解码

查找解码器

else if (enc->codec_type == AVMEDIA_TYPE_AUDIO)
{
    audioStream = i;
    
    // 查找解码器
    AVCodec *codec = avcodec_find_decoder(enc->codec_id);
    if (avcodec_open2(enc, codec, NULL) < 0)
    {
        mutex.unlock();
        return 0;
    }
    
    this->sampleRate = enc->sample_rate;  // 样本率
    this->channel = enc->channels;        // 通道数
    switch (enc->sample_fmt)              // 样本大小
    {
        case AV_SAMPLE_FMT_S16:   //signed 16 bits
            this->sampleSize = 16;
            break;
        case  AV_SAMPLE_FMT_S32:  //signed 32 bits
            this->sampleSize = 32;
        default:
            break;
    }
}

音频解码

通过avcodec_send_packet接口同时发送音视频,avcodec_receive_frame接收解码后的数据。通过一个pts来保存当前解码帧的时间,来做音视频同步。

int XFFmpeg::Decode(const AVPacket *pkt)
{
	mutex.lock();
	if (!ic)//若未打开视频
	{
		mutex.unlock();
		return NULL;

	}
    
	if (NULL == yuv) 
	{
        // 申请解码的对象空间
		yuv = av_frame_alloc();
	}
    
	if (NULL == pcm)
	{
		pcm = av_frame_alloc();
	}
    
	AVFrame *frame = yuv;// 此时的frame是解码后的视频流
	if (pkt->stream_index == audioStream)
	{
        // 此时frame是解码后的音频流
		frame = pcm;
	}
    
    // 发送之前读取的pkt
	int re = avcodec_send_packet(ic->streams[pkt->stream_index]->codec, pkt);
	if (0 != re)
	{
		mutex.unlock();
		return NULL;
	}
    
    // 解码pkt后存入yuv中
	re = avcodec_receive_frame(ic->streams[pkt->stream_index]->codec, frame);
	if (0 != re)
	{
		mutex.unlock();
		return NULL;
	}
	qDebug() << "pts=" << frame->pts;
	
	mutex.unlock();
    
    // 当前解码的显示时间
    int p = frame->pts*r2d(ic->streams[pkt->stream_index]->time_base);
	if (pkt->stream_index == audioStream) // 为音频流时设置pts
		this->pts = p;

	return p;
}

音频重采样

av_samples_get_buffer_size:计算音频占用的字节数。

int XFFmpeg::ToPCM(char *out)
{
	mutex.lock();
	if (!ic || !pcm || !out)
	{
		mutex.unlock();
		return 0;
	}
    
    //音频解码器上下文
	AVCodecContext *ctx = ic->streams[audioStream]->codec;
	if (NULL == aCtx)
	{
		aCtx = swr_alloc();//初始化
		swr_alloc_set_opts(aCtx,ctx->channel_layout,
			AV_SAMPLE_FMT_S16,
			  ctx->sample_rate,
			  ctx->channels,
			  ctx->sample_fmt,
			  ctx->sample_rate,
			  0,0
			  );
		swr_init(aCtx);
	}
    
	uint8_t  *data[1];
	data[0] = (uint8_t *)out;
	int len = swr_convert(aCtx, data, 10000,
		(const uint8_t **)pcm->data,
		pcm->nb_samples
		);
    
	if (len <= 0)
	{
		mutex.unlock();
		return 0;
	}
    
    // 获取音频占用的字节数
	int outsize = av_samples_get_buffer_size(NULL, ctx->channels,
		pcm->nb_samples,
		AV_SAMPLE_FMT_S16,
		0);

	mutex.unlock();
	return outsize;
}

音频播放

void XVideoThread::run()
{
	char out[10000] = {0};
	while (!isexit)
	{
        // 如果为暂停状态,不处理
		if (!XFFmpeg::Get()->isPlay)
		{
			msleep(10);
			continue;
		}
        
        //确定list中是否有AVpacket包
		while (videos.size()>0)
		{
			AVPacket pack = videos.front();//每次取出list中的第一个AVPack包
            
            //获得该包的pts
			int pts = XFFmpeg::Get()->GetPts(&pack);
			if (pts > apts)//若视频包大于音频包的pts,结束
			{
				break;
			}
            
            //解码视频帧
	 		XFFmpeg::Get()->Decode(&pack);
            
            //清理该AVPacket包
			av_packet_unref(&pack);
            
            //从list链表中删除
			videos.pop_front();
		}

        //此时缓冲区的空间大小
		int free = XAudioPlay::Get()->GetFree();
		if (free < 10000)
		{
			msleep(1);
			continue;
		}
        
        // 数据包读取
		AVPacket pkt = XFFmpeg::Get()->Read();
		if (pkt.size <= 0)//未打开视频
		{
			msleep(10);
			continue;
		}
        
		if (pkt.stream_index == XFFmpeg::Get()->audioStream)
		{
            //解码音频
			apts = XFFmpeg::Get()->Decode(&pkt);
            
            //释放pkt包
			av_packet_unref(&pkt);
            
            //重采样音频
			int len = XFFmpeg::Get()->ToPCM(out);
            
            //写入音频
			XAudioPlay::Get()->Write(out, len);
			continue;
		}
// 		XFFmpeg::Get()->Decode(&pkt);//解码视频帧
// 		av_packet_unref(&pkt); 
		videos.push_back(pkt);
	}
}

获取pts时间

int XFFmpeg::GetPts(const AVPacket *pkt)
{
	mutex.lock();
	if (!ic)
	{
		mutex.unlock();
		return -1;
	}
    
	int pts = pkt->pts*r2d(ic->streams[pkt->stream_index]->time_base);
	mutex.unlock();
	return pts;
}

音频名词解释

PCM

 PCM(Pulse Code Modulation),即脉冲编码调制,对声音进行采样、量化过程,未经过任何编码和压缩处理。
 
 PCM数据是最原始的音频数据完全无损,所以PCM数据虽然音质优秀但体积庞大,为了解决这个问题先后诞生了一系列的音频格式,这些音频格式运用不同的方法对音频数据进行压  缩,其中有无损压缩(ALAC、APE、FLAC)和有损压缩(MP3、AAC、OGG、WMA)两种。

采样

波是无限光滑的,采样的过程就是从波中抽取某些点的频率值,就是把模拟信号数字化

采样频率

单位时间内对模拟信号的采样次数,它用赫兹(Hz)来表示。

8KHz - 电话所用采样率, 对于人的说话已经足够
22.05KHz - 只能达到FM广播的声音品质(适用于语音和中等品质的音乐)
44.1KHz - 最常见的采样率标准,理论上的CD音质界限
48KHz - 更加精确一些

重采样

在进行模拟/数字信号的转换过程中,当采样频率大于信号中最高频率的2倍时,采样之后的数字信号完整地保留了原始信号中的信息,一般实际应用中保证采样频率为信号是最高频率的5~10倍。采样定理又称奈奎斯特定理。

采样位数

每个采样点能够表示的数据范围。采样位数通常有8bits或16bits两种,采样位数越大,所能记录声音的变化度就越细腻,相应的数据量就越大。

8位字长量化(低品质)和16位字长量化(高品质),16 bit 是最常见的采样精度。

声道数

声道数是指支持能不同发声的音响的个数,它是衡量音响设备的重要指标之一。

单声道的声道数为1个声道;
双声道的声道数为2个声道;
立体声道的声道数默认为2个声道;
立体声道(4声道)的声道数为4个声道。

部分参考:

https://blog.csdn.net/houxiaoni01/article/details/109175486

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值