前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >ffmpeg 音频播放器相关

ffmpeg 音频播放器相关

作者头像
曾大稳
发布2018-09-11 10:42:10
1.9K0
发布2018-09-11 10:42:10
举报
文章被收录于专栏:曾大稳的博客
每秒理论PCM大小
代码语言:javascript
复制
每秒理论PCM大小 = 采样率 * 声道数 * 位数/8

比如:

代码语言:javascript
复制
//44100hz 立体声 16bit
int s_time = 44100 * 2 * 16/8;
获取总时长
代码语言:javascript
复制
duration = pFormatCtx->duration / AV_TIME_BASE;
获取当前AVframe时间
代码语言:javascript
复制

AVRational time_base = pFormatCtx->streams[audio_index]->time_base
double now_time = frame->pts * av_q2d(time_base);
获取当前播放时间

因为每一个AVframepts不一定都有,所以就需要自己手维护一个当前时间的变量

代码语言:javascript
复制
公式:PCM实际数据大小 / 每秒理论PCM大小;
clock += buffersize / ((double)(sample_rate * 2 * 2));

伪代码如下:

代码语言:javascript
复制
AVFormatContext *pFormatCtx = NULL;
//采样率
int sample_rate =0;
//当前总时长
int duration = 0;

AVRational time_base =NULL;
//当前AvFrame时间
int now_time=0;
//当前播放时长
int clock = 0;
//上次播放时长标识
int last_time=0;

uint8_t *buffer = NULL;
int data_size=0;


//获取到 pFormatCtx
...
for(int i = 0; i < pFormatCtx->nb_streams; i++)
    {
        if(pFormatCtx->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_AUDIO)//得到音频流
        {
                ...
                sample_rate = pFormatCtx->streams[i]->codecpar->sample_rate;
                duration= pFormatCtx->duration / AV_TIME_BASE;
                time_base = pFormatCtx->streams[i]->time_base;
        }
}

//解码数据  
...
    
  int nb = swr_convert(
                swr_ctx,
                &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);
   data_size = nb * out_channels * av_get_bytes_per_sample(AV_SAMPLE_FMT_S16);

    now_time = avFrame->pts * av_q2d(time_base);
    if(now_time < clock){
       now_time = clock;
    }
    clock = now_time;


//解码数据之后数据封装在 buffer,播放的时候
...

if(data_size>0){
    //size/(采样率(44100hz) * 立体声*16bit/8) 
    clock += data_size / ((double)(sample_rate * 2 * 2));
    //设置一个回掉最小相差值
    if(clock - last_tiem >= 0.1) {
        last_tiem = clock;
        //回调应用层
       callJava->onCallTimeInfo(CHILD_THREAD,clock, duration);
    } 
}
解码播放流程思路

采用多线程,生产者消费者模型,AVPacket入队,然后AVPacket出队解码播放,播放采用OpenSLES

release内存回收

当我们release的时候,我们需要注意

  1. 为了确保线程完全退出,我们最好是sleep个几十毫秒,然后在释放相关内存,但是最好的是使用pthred_join来同步线程退出。
  2. 有可能初始化未准备完毕我们就调用release,这时候最好是在初始化准备和 release加个线程锁。
  3. 初始化的时候有可能avformat_open_input打开网络链接,网络很卡,所以我们需要为pFormatCtx加入一个interrupt_callback来及时响应int avformat_callback(void *ctx) { WlFFmpeg *fFmpeg = (WlFFmpeg *) ctx; if(fFmpeg->playstatus->exit) { return AVERROR_EOF; } return 0; } pFormatCtx->interrupt_callback.callback = avformat_callback; pFormatCtx->interrupt_callback.opaque = this;
暂停,继续,停止播放,播放完成

暂停播放,继续播放采用OpenSLES的相关api,播放完成则在播放完毕的时候回掉即可

seek功能

seek的时候设置标志位并加锁,清空队列,标志位判断是否继续av_read_frameseek完毕释放锁,还原标识位。即可重新读取最新数据

代码语言:javascript
复制

seek = true;
queue->clearAvpacket();

pthread_mutex_lock(&seek_mutex);
int64_t rel = secs * AV_TIME_BASE;
//重置内部解码器状态/刷新内部缓冲区
 avcodec_flush_buffers(avCodecContext);
//主要是这个函数
avformat_seek_file(pFormatCtx, -1, INT64_MIN, rel, INT64_MAX, 0);
pthread_mutex_unlock(&seek_mutex);
seek = false;

pthread_mutex_lock(&seek_mutex);
ret = av_read_frame(pFormatCtx, packet);
pthread_mutex_unlock(&seek_mutex);
音量,声道切换

采用OpenSLES的相关api

播放变速变调

OpenSL ES可以实现变速播放,但是再改变速度的同时也改变了音调,这 种体验是不好的。所以采用SoundTouch来实现,在播放的时候,对原始数据重新进行计算即可

计算pcm分贝大小
代码语言:javascript
复制

//char*是为了都转换成字节来处理 
int WlAudio::getPCMDB(char *pcmcata, size_t pcmsize) {
    int db = 0;
    short int pervalue = 0;
    double sum = 0;
    for (int i = 0; i < pcmsize; i += 2) {
        memcpy(&pervalue, pcmcata + i, 2);
        sum += abs(pervalue);
    }
    sum = sum / (pcmsize / 2);
    if (sum > 0) {
        db = (int) 20.0 * log10(sum);
    }
    return db;
}
性能优化
  1. 由于解码用到了while循环,而不加睡眠的while循环会使CPU使用率提高30%左右, 因此我们需要为解码线程加上一定的睡眠时间来降低CPU使用率。
  2. 停止时回收创建的内存空间。
一个AVPacket对应多个AVFrame 比如.ape格式的

这种情况就需要在解码的时候,设置一个标识来判断不停的解析AVPacket,avcodec_send_packet(avCodecContext, avPacket)之后不停的avcodec_receive_frame,知道读取完毕在设置标识。

一个AVPacket对应多个AVFrame引发的seek问题

由于一个AVPacket里面有多个AVFrame,当seek时,FFmpeg解码器中还残留AVFrame,所以会导致seek后,不能立即播放当前音乐。 解决方案就是seek的时候调用

代码语言:javascript
复制
avcodec_flush_buffers(avCodecContext)
本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2018-06-26,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 每秒理论PCM大小
  • 获取总时长
  • 获取当前AVframe时间
  • 获取当前播放时间
  • 解码播放流程思路
  • release内存回收
  • 暂停,继续,停止播放,播放完成
  • seek功能
  • 音量,声道切换
  • 播放变速变调
  • 计算pcm分贝大小
  • 性能优化
  • 一个AVPacket对应多个AVFrame 比如.ape格式的
  • 一个AVPacket对应多个AVFrame引发的seek问题
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档