文章目录
- 一 暂停
- 1 原理
- 2 代码实现
- 二 继续播放
- 1 原理
- 2 代码实现
- 三 停止
- 1 手动停止
- 2 自动停止
- 三 防止重复启动
- 四 Demo
一 暂停
1 原理
- AudioChannel和VideoChannel解码时遇到暂停标记进行等待
- AudioChannel和VideoChannel存放packet的队列会被FFmpeg存满,FFmpeg就会一直等待
- 存放frame的队列播放完了之后,就会一直在队列pop时等待
流解析、音视频解码、音视频渲染就一直在等待,从而实现了暂停功能
2 代码实现
- 设置标记
void DNFFmpeg::pause() {
if (audioChannel) {
audioChannel->pause();
}
if (videoChannel) {
LOGE("启动了锁1");
videoChannel->pause();
}
};
void AudioChannel::pause() {
isPause = 1;
}
void VideoChannel::pause() {
LOGE("启动了锁2");
isPause = 1;
}
- 音视频解码遇到标记停止
void VideoChannel::render() {
pthread_mutex_lock(&mutex);
...
while (isPlaying) {
if(isPause){
LOGE("启动了锁3");
pthread_cond_wait(&cond, &mutex);
}
...
}
pthread_mutex_unlock(&mutex);
...
}
int AudioChannel::getPcm() {
....
pthread_mutex_lock(&mutex);
while (isPlaying) {
if(isPause){
pthread_cond_wait(&cond, &mutex);
}
...
}
pthread_mutex_unlock(&mutex);
}
- 音视频解码暂停,导致解码存储的frame队列变空;空的frame队列在取的时候会进入等待
safe_queue
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;
}
- 由于Decode等待,FFmpeg解析音视频后存储的集合packet会满,从而导致FFmpeg的解析也会停止
**
* 专门读取数据包
*/
void DNFFmpeg::_start() {
//1、读取媒体数据包(音视频数据包)
int ret = 0;
while (isPlaying) {
//读取文件的时候没有网络请求,一下子读完了,可能导致oom
//特别是读本地文件的时候 一下子就读完了
if (audioChannel && audioChannel->packets.size() > 100) {
//10ms
av_usleep(1000 * 10);
continue;
}
if (videoChannel && videoChannel->packets.size() > 100) {
av_usleep(1000 * 10);
continue;
}
...
}
二 继续播放
1 原理
唤醒音视频Decode时上的锁,设置标记,由于FFmpeg的读取在av_usleep,音视频的渲染也在循环,所以播放继续
2 代码实现
void DNFFmpeg::resume() {
if (audioChannel) {
audioChannel->resume();
}
if (videoChannel) {
videoChannel->resume();
}
};
void AudioChannel::resume() {
isPause = 0;
pthread_cond_signal(&cond);
}
``
```cpp
void VideoChannel::resume() {
isPause = 0;
pthread_cond_signal(&cond);
}
三 停止
1 手动停止
void DNFFmpeg::stop() {
callHelper = 0;
if (audioChannel) {
audioChannel->javaCallHelper = 0;
}
if (videoChannel) {
videoChannel->javaCallHelper = 0;
}
// formatContext
pthread_create(&pid_stop, 0, async_stop, this);
}
void *async_stop(void *args) {
DNFFmpeg *ffmpeg = static_cast<DNFFmpeg *>(args);
// 等待prepare结束
if (ffmpeg->pid) {
pthread_join(ffmpeg->pid, 0);
}
ffmpeg->pid = 0;
ffmpeg->isPlaying = 0;
// 保证 start线程结束
if (ffmpeg->pid_play) {
pthread_join(ffmpeg->pid_play, 0);
}
ffmpeg->pid_play = 0;
DELETE(ffmpeg->videoChannel);
DELETE(ffmpeg->audioChannel);
// 这时候释放就不会出现问题了
if (ffmpeg->formatContext) {
//先关闭读取 (关闭fileintputstream)
avformat_close_input(&ffmpeg->formatContext);
avformat_free_context(ffmpeg->formatContext);
ffmpeg->formatContext = 0;
}
DELETE(ffmpeg);
return 0;
}
void VideoChannel::stop() {
isPlaying = 0;
frames.setWork(0);
packets.setWork(0);
pthread_join(pid_decode, 0);
pthread_join(pid_render, 0);
}
void AudioChannel::stop() {
isPlaying = 0;
packets.setWork(0);
frames.setWork(0);
pthread_join(pid_audio_decode, 0);
pthread_join(pid_audio_play, 0);
if (swrContext) {
swr_free(&swrContext);
swrContext = 0;
}
//释放播放器
if (bqPlayerObject) {
(*bqPlayerObject)->Destroy(bqPlayerObject);
bqPlayerObject = 0;
bqPlayerInterface = 0;
bqPlayerBufferQueueInterface = 0;
}
//释放混音器
if (outputMixObject) {
(*outputMixObject)->Destroy(outputMixObject);
outputMixObject = 0;
}
//释放引擎
if (engineObject) {
(*engineObject)->Destroy(engineObject);
engineObject = 0;
engineInterface = 0;
}
}
2 自动停止
DNFFmpeg没有数据可以读取是,跳出循环,结束
void DNFFmpeg::_start() {
while (isPlaying) {
...
//=0成功 其他:失败
if (ret == 0) {
...
}else if (ret == AVERROR_EOF) {
//读取完成 但是可能还没播放完
}else {
//结束
break;
}
}
isPlaying = 0;
audioChannel->stop();
videoChannel->stop();
}
三 防止重复启动
画面多次渲染,声音重复,设置标记判断即可
void DNFFmpeg::start() {
if (isPlaying) {
return;
}
isPlaying = 1;
//启动声音的解码与播放
if (audioChannel) {
audioChannel->play();
}
if (videoChannel) {
videoChannel->setAudioChannel(audioChannel);
videoChannel->play();
}
pthread_create(&pid_play, NULL, play, this);
}
void DNFFmpeg::start() {
if (isPlaying) {
return;
}
isPlaying = 1;
...
}
为了能够在停止后,再次启动播放,需要结束播放时,标记重置
void DNFFmpeg::_start()
while (isPlaying) {
//播放
}
//结束播放
isPlaying = 0;
audioChannel->stop();
videoChannel->stop();
};