NDK27_FFmpeg音视频同步
程序员文章站
2022-07-01 15:37:07
...
前两章分别实现了视频和音频的解码与播放
NDK25_FFmpeg视频解码与原生绘制
NDK27_FFmpeg音频解码与OpenSL播放
发现出现了不同步的请求,需要对音视频进行同步
一 音视频同步方式
- 将视频根据音频同步(以音频为主)
- 以视频为主
- 以一个外部事件进度为主
本文以音频为主,因为其实现方式比较简单
二 重点API
- AudioChannel 的frame能够获取一个相对播放时间
clock = frame->pts*av_q2d(time_base);
- 将AudioChannel 的引用传给VideoChannel
//注意:audioChannel进行初始化(使用的时候可以判空)
if (videoChannel) {
videoChannel->setAudioChannel(audioChannel);
videoChannel->play();
}
- VideoChannel 的frames设置同步操作
frames.setSyncHandle(dropAvFrame);
//当 frames.sync();时,就会执行dropAvFrame(),线程安全的
- VideoChannel 进行播放render时进行音视频同步
//获得当前这一画面 播放的相对时间
//大多数情况下best_effort_timestamp的值 和pts一样
double clock = frame->best_effort_timestamp * av_q2d(time_base);
//额外的间隔时间
double extra_delay = frame->repeat_pict / (2 * fps);
//真实的间隔时间
double delays = extra_delay + frame_delays;
if (!audioChannel) {
//休眠
//视频快了
//av_usleep(frame_delays*1000000+x);
//视频慢了
//av_usleep(frame_delays*1000000-x);
av_usleep(delays * 1000000);
} else {
if (clock == 0) {
av_usleep(delays * 1000000);
} else {
//比较音频与视频
double audioClock = audioChannel->clock;
//音视频相差的间隔
double diff = clock - audioClock;
//大于0表示视频比较快
//小于0表示音频比较快
if (diff > 0) {
LOGE("视频快了:%lf",diff);
av_usleep((delays + diff) * 1000000);
} else if (diff < 0) {
LOGE("音频快了:%lf",diff);
//视频包挤压的太多了(丢包)
if (fabs(diff) >= 0.05) {
releaseAvFrame(&frame);
//丢包
frames.sync();
continue;
}
//不睡了 快点赶上音频
}
}
}
//回调出去进行播放
callback(dst_data[0], dst_linesize[0], avCodecContext->width, avCodecContext->height);
releaseAvFrame(&frame);
}
- dropAvFrame 丢已经解码的图片
void dropAvFrame(queue<AVFrame *> &q) {
if (!q.empty()) {
AVFrame *frame = q.front();
BaseChannel::releaseAvFrame(&frame);
q.pop();
}
}
三 代码
AudioChannel
//
// Created by PF0ZYBAJ on 2020-9-9.
//
#include "AudioChannel.h"
void *audio_decode(void *args) {
AudioChannel *audioChannel = static_cast<AudioChannel *>(args);
audioChannel->decode();
return 0;
}
void *audio_play(void *args) {
AudioChannel *audioChannel = static_cast<AudioChannel *>(args);
audioChannel->_play();
return 0;
}
AudioChannel::AudioChannel(int id, AVCodecContext *context,AVRational time_base) : BaseChannel(id, context,time_base) {
//根据布局获取声道数
out_channels = av_get_channel_layout_nb_channels(AV_CH_LAYOUT_STEREO);
out_samplesize = av_get_bytes_per_sample(AV_SAMPLE_FMT_S16);
out_sample_rate = 44100;
//44100个16位 44100 * 2
// 44100*(双声道)*(16位)
data = static_cast<uint8_t *>(malloc(out_sample_rate * out_channels * out_samplesize));
memset(data,0,out_sample_rate * out_channels * out_samplesize);
}
AudioChannel::~AudioChannel() {
if (data) {
free(data);
data = 0;
}
}
void AudioChannel::play() {
//设置为播放状态
packets.setWork(1);
frames.setWork(1);
//0+输出声道+输出采样位+输出采样率 +输入的3个参数
swrContext = swr_alloc_set_opts(0, AV_CH_LAYOUT_STEREO, AV_SAMPLE_FMT_S16, out_sample_rate,
avCodecContext->channel_layout, avCodecContext->sample_fmt,
avCodecContext->sample_rate, 0, 0);
swr_init(swrContext);
isPlaying = 1;
//1 、解码
pthread_create(&pid_audio_decode, 0, audio_decode, this);
//2、 播放
pthread_create(&pid_audio_play, 0, audio_play, this);
}
void AudioChannel::decode() {
AVPacket *packet = 0;
while (isPlaying) {
//取出一个数据包
int ret = packets.pop(packet);
if (!isPlaying) {
break;
}
//取出失败
if (!ret) {
continue;
}
//把包丢给解码器
ret = avcodec_send_packet(avCodecContext, packet);
releaseAvPacket(&packet);
//重试
if (ret != 0) {
break;
}
//代表了一个图像 (将这个图像先输出来)
AVFrame *frame = av_frame_alloc();
//从解码器中读取 解码后的数据包 AVFrame
ret = avcodec_receive_frame(avCodecContext, frame);
//需要更多的数据才能够进行解码
if (ret == AVERROR(EAGAIN)) {
continue;
} else if (ret != 0) {
break;
}
//再开一个线程 来播放 (流畅度)
frames.push(frame);
}
releaseAvPacket(&packet);
};
//返回获取的pcm数据大小
int AudioChannel::getPcm() {
int data_size = 0;
AVFrame *frame;
int ret = frames.pop(frame);
if (!isPlaying) {
if (ret) {
releaseAvFrame(&frame);
}
return data_size;
}
//4800HZ 8位 -》44100 16 位
//重采样
//假设我们输入了10个数据,swrContext转码器这一次处理了8个数据
//那么如果不加delays(上次没处理完的数据),积压
int64_t delays = swr_get_delay(swrContext,frame->sample_rate);
//将frame->nb_samples 个数据由sample_rate采样率转成44100 后 返回多少个数据
//10 个 4800 = nb 个44100
int64_t max_samples = av_rescale_rnd(delays+frame->nb_samples,out_sample_rate,frame->sample_rate,AV_ROUND_UP);
//上下文+输出缓冲区+输出缓冲区能接受的最大数据量+输入数据+输入数据个数
//返回每一个声道的输出数据
int samples = swr_convert(swrContext, &data, max_samples, (const uint8_t **)frame->data, frame->nb_samples);
//44100*2(声道数)多少个16位数据
//获得 samples个 2字节(16位)*2声道
data_size = samples * out_samplesize * out_channels ;
//获取frame的一个相对时间(相对开始播放)
//获得相对播放这一段时间的秒数
clock = frame->pts*av_q2d(time_base);
return data_size;
}
void bqPlayerCallback(SLAndroidSimpleBufferQueueItf bq, void *context) {
AudioChannel *audioChannel = static_cast<AudioChannel *>(context);
//获得pcm 数据 多少个字节 data
int dataSize = audioChannel->getPcm();
if(dataSize > 0 ){
// 接收16位数据
(*bq)->Enqueue(bq,audioChannel->data,dataSize);
}
}
void AudioChannel::_play() {
/**
* 1 创建引擎并获取引擎接口
*/
SLresult result;
//1.1 创建引擎
result = slCreateEngine(&engineObject, 0, NULL, 0, NULL, NULL);
if (SL_RESULT_SUCCESS != result) {
return;
}
//1.2 初始化引擎
result = (*engineObject)->Realize(engineObject, SL_BOOLEAN_FALSE);
if (SL_RESULT_SUCCESS != result) {
return;
}
//1.3 获取引擎接口SLEngineItf engineInterface
result = (*engineObject)->GetInterface(engineObject, SL_IID_ENGINE, &engineInterface);
if (SL_RESULT_SUCCESS != result) {
return;
}
/**
* 2 设置混音器
*
*/
//2.1 创建混音器SLObjectItf outputMixObject
result = (*engineInterface)->CreateOutputMix(engineInterface, &outputMixObject, 0,
0, 0);
if (SL_RESULT_SUCCESS != result) {
return;
}
//2.2 初始化混音器outputMixObject
result = (*outputMixObject)->Realize(outputMixObject, SL_BOOLEAN_FALSE);
if (SL_RESULT_SUCCESS != result) {
return;
}
/**
* 3 创建播放器
*/
//3.1 匹配输入声音信息
//创建buffer缓冲类型的队列 2个队列
SLDataLocator_AndroidSimpleBufferQueue android_queue = {SL_DATALOCATOR_ANDROIDSIMPLEBUFFERQUEUE,
2};
//pcm数据格式
//pcm+2(双声道)+44100(采样率)+ 16(采样位)+16(数据的大小)+LEFT|RIGHT(双声道)+小端数据
SLDataFormat_PCM pcm = {SL_DATAFORMAT_PCM, 2, SL_SAMPLINGRATE_44_1, SL_PCMSAMPLEFORMAT_FIXED_16,
SL_PCMSAMPLEFORMAT_FIXED_16,
SL_SPEAKER_FRONT_LEFT | SL_SPEAKER_FRONT_RIGHT,
SL_BYTEORDER_LITTLEENDIAN};
//数据源 将上述配置信息放到这个数据源中
SLDataSource slDataSource = {&android_queue, &pcm};
//3.2 配置音轨(输出)
//设置混音器
SLDataLocator_OutputMix outputMix = {SL_DATALOCATOR_OUTPUTMIX, outputMixObject};
SLDataSink audioSnk = {&outputMix, NULL};
//需要的接口 操作队列的接口
const SLInterfaceID ids[1] = {SL_IID_BUFFERQUEUE};
const SLboolean req[1] = {SL_BOOLEAN_TRUE};
//3.3 创建播放器
(*engineInterface)->CreateAudioPlayer(engineInterface, &bqPlayerObject, &slDataSource,
&audioSnk, 1,
ids, req);
//初始化播放器
(*bqPlayerObject)->Realize(bqPlayerObject, SL_BOOLEAN_FALSE);
//得到接口后调用 获取Player接口
(*bqPlayerObject)->GetInterface(bqPlayerObject, SL_IID_PLAY, &bqPlayerInterface);
/**
* 4 设置播放回调函数
*/
//获取播放器队列接口
(*bqPlayerObject)->GetInterface(bqPlayerObject, SL_IID_BUFFERQUEUE,
&bqPlayerBufferQueueInterface);
//设置回调
(*bqPlayerBufferQueueInterface)->RegisterCallback(bqPlayerBufferQueueInterface,
bqPlayerCallback, this);
/**
* 5设置播放状态
*/
(*bqPlayerInterface)->SetPlayState(bqPlayerInterface, SL_PLAYSTATE_PLAYING);
/**
* 6 手动**一下这个回调
*/
bqPlayerCallback(bqPlayerBufferQueueInterface, this);
}
VideoChannel
//
// Created by Administrator on 2018/9/5.
//
extern "C" {
#include <libavutil/imgutils.h>
#include <libavutil/time.h>
}
#include "VideoChannel.h"
#include "macro.h"
void *decode_task(void *args) {
VideoChannel *channel = static_cast<VideoChannel *>(args);
channel->decode();
return 0;
}
void *render_task(void *args) {
VideoChannel *channel = static_cast<VideoChannel *>(args);
channel->render();
return 0;
}
/**
* 丢包 直到下一个关键帧
* @param q
*/
void dropAvPacket(queue<AVPacket *> &q) {
while (!q.empty()) {
AVPacket *packet = q.front();
//如果不属于I帧
if (packet->flags != AV_PKT_FLAG_KEY) {
BaseChannel::releaseAvPacket(&packet);
q.pop();
} else {
break;
}
}
}
/**
* 丢已经解码的图片
*/
void dropAvFrame(queue<AVFrame *> &q) {
if (!q.empty()) {
AVFrame *frame = q.front();
BaseChannel::releaseAvFrame(&frame);
q.pop();
}
}
VideoChannel::VideoChannel(int id, AVCodecContext *avCodecContext, AVRational time_base, int fps)
: BaseChannel(id,
avCodecContext, time_base) {
this->fps = fps;
// 用于设置一个 同步操作 队列的一个函数指针
// packets.setSyncHandle(dropAvPacket);
frames.setSyncHandle(dropAvFrame);
}
VideoChannel::~VideoChannel() {
}
void VideoChannel::setAudioChannel(AudioChannel *audioChannel) {
this->audioChannel = audioChannel;
}
void VideoChannel::play() {
isPlaying = 1;
//设置为工作状态
frames.setWork(1);
packets.setWork(1);
//1、解码
pthread_create(&pid_decode, 0, decode_task, this);
//2、播放
pthread_create(&pid_render, 0, render_task, this);
}
//解码
void VideoChannel::decode() {
AVPacket *packet = 0;
while (isPlaying) {
//取出一个数据包
int ret = packets.pop(packet);
if (!isPlaying) {
break;
}
//取出失败
if (!ret) {
continue;
}
//把包丢给解码器
ret = avcodec_send_packet(avCodecContext, packet);
releaseAvPacket(&packet);
if (ret == AVERROR_INVALIDDATA) {
continue;
}
//重试
if (ret != 0) {
break;
}
//代表了一个图像 (将这个图像先输出来)
AVFrame *frame = av_frame_alloc();
//从解码器中读取 解码后的数据包 AVFrame
ret = avcodec_receive_frame(avCodecContext, frame);
//需要更多的数据才能够进行解码
if (ret == AVERROR(EAGAIN)) {
continue;
} else if (ret != 0) {
break;
}
//再开一个线程 来播放 (流畅度)
frames.push(frame);
}
releaseAvPacket(&packet);
}
//播放
void VideoChannel::render() {
//目标: RGBA
swsContext = sws_getContext(
avCodecContext->width, avCodecContext->height, avCodecContext->pix_fmt,
avCodecContext->width, avCodecContext->height, AV_PIX_FMT_RGBA,
SWS_BILINEAR, 0, 0, 0);
//每个画面刷新的间隔 单位:秒
double frame_delays = 1.0 / fps;
AVFrame *frame = 0;
//指针数组
uint8_t *dst_data[4];
int dst_linesize[4];
av_image_alloc(dst_data, dst_linesize,
avCodecContext->width, avCodecContext->height, AV_PIX_FMT_RGBA, 1);
while (isPlaying) {
int ret = frames.pop(frame);
if (!isPlaying) {
break;
}
//src_linesize: 表示每一行存放的 字节长度
sws_scale(swsContext, reinterpret_cast<const uint8_t *const *>(frame->data),
frame->linesize, 0,
avCodecContext->height,
dst_data,
dst_linesize);
//获得当前这一画面 播放的相对时间
//大多数情况下best_effort_timestamp的值 和pts一样
double clock = frame->best_effort_timestamp * av_q2d(time_base);
//额外的间隔时间
double extra_delay = frame->repeat_pict / (2 * fps);
//真实的间隔时间
double delays = extra_delay + frame_delays;
if (!audioChannel) {
//休眠
//视频快了
//av_usleep(frame_delays*1000000+x);
//视频慢了
//av_usleep(frame_delays*1000000-x);
av_usleep(delays * 1000000);
} else {
if (clock == 0) {
av_usleep(delays * 1000000);
} else {
//比较音频与视频
double audioClock = audioChannel->clock;
//音视频相差的间隔
double diff = clock - audioClock;
//大于0表示视频比较快
//小于0表示音频比较快
if (diff > 0) {
LOGE("视频快了:%lf",diff);
av_usleep((delays + diff) * 1000000);
} else if (diff < 0) {
LOGE("音频快了:%lf",diff);
//视频包挤压的太多了(丢包)
if (fabs(diff) >= 0.05) {
releaseAvFrame(&frame);
//丢包
frames.sync();
continue;
}
//不睡了 快点赶上音频
}
}
}
//回调出去进行播放
callback(dst_data[0], dst_linesize[0], avCodecContext->width, avCodecContext->height);
releaseAvFrame(&frame);
}
av_freep(&dst_data[0]);
releaseAvFrame(&frame);
}
void VideoChannel::setRenderFrameCallback(RenderFrameCallback callback) {
this->callback = callback;
}
SafeQueue(设置同步操作)
#ifndef DNRECORDER_SAFE_QUEUE_H
#define DNRECORDER_SAFE_QUEUE_H
#include <queue>
#include <pthread.h>
//#define C11
#ifdef C11
#include <thread>
#endif
using namespace std;
template<typename T>
class SafeQueue {
typedef void (*ReleaseCallback)(T *);
typedef void (*SyncHandle)(queue<T> &);
public:
SafeQueue() {
#ifdef C11
#else
pthread_mutex_init(&mutex, NULL);
pthread_cond_init(&cond, NULL);
#endif
}
~SafeQueue() {
#ifdef C11
#else
pthread_cond_destroy(&cond);
pthread_mutex_destroy(&mutex);
#endif
}
void push(const T new_value) {
#ifdef C11
//锁 和智能指针原理类似,自动释放
lock_guard<mutex> lk(mt);
if (work) {
q.push(new_value);
cv.notify_one();
}
#else
pthread_mutex_lock(&mutex);
if (work) {
q.push(new_value);
pthread_cond_signal(&cond);
pthread_mutex_unlock(&mutex);
}
pthread_mutex_unlock(&mutex);
#endif
}
int pop(T& value) {
int ret = 0;
#ifdef C11
//占用空间相对lock_guard 更大一点且相对更慢一点,但是配合条件必须使用它,更灵活
unique_lock<mutex> lk(mt);
//第二个参数 lambda表达式:false则不阻塞 往下走
cv.wait(lk,[this]{return !work || !q.empty();});
if (!q.empty()) {
value = q.front();
q.pop();
ret = 1;
}
#else
pthread_mutex_lock(&mutex);
//在多核处理器下 由于竞争可能虚假唤醒 包括jdk也说明了
while (work && q.empty()) {
pthread_cond_wait(&cond, &mutex);
}
if (!q.empty()) {
value = q.front();
q.pop();
ret = 1;
}
pthread_mutex_unlock(&mutex);
#endif
return ret;
}
void setWork(int work) {
#ifdef C11
lock_guard<mutex> lk(mt);
this->work = work;
#else
pthread_mutex_lock(&mutex);
this->work = work;
pthread_cond_signal(&cond);
pthread_mutex_unlock(&mutex);
#endif
}
int empty() {
return q.empty();
}
int size() {
return q.size();
}
void clear() {
#ifdef C11
lock_guard<mutex> lk(mt);
int size = q.size();
for (int i = 0; i < size; ++i) {
T value = q.front();
releaseHandle(value);
q.pop();
}
#else
pthread_mutex_lock(&mutex);
int size = q.size();
for (int i = 0; i < size; ++i) {
T value = q.front();
releaseCallback(&value);
q.pop();
}
pthread_mutex_unlock(&mutex);
#endif
}
void sync() {
#ifdef C11
lock_guard<mutex> lk(mt);
syncHandle(q);
#else
pthread_mutex_lock(&mutex);
//同步代码块 当我们调用sync方法的时候,能够保证是在同步块中操作queue 队列
syncHandle(q);
pthread_mutex_unlock(&mutex);
#endif
}
void setReleaseCallback(ReleaseCallback r) {
releaseCallback = r;
}
void setSyncHandle(SyncHandle s) {
syncHandle = s;
}
private:
#ifdef C11
mutex mt;
condition_variable cv;
#else
pthread_cond_t cond;
pthread_mutex_t mutex;
#endif
queue<T> q;
//是否工作的标记 1 :工作 0:不接受数据 不工作
int work;
ReleaseCallback releaseCallback;
SyncHandle syncHandle;
};
#endif //DNRECORDER_SAFE_QUEUE_H
下一篇: webrtc学习笔记四(获取真实的ip)