基于ffmpeg网络播放器的教程与总结

时间:2024.4.8

基于ffmpeg网络播放器的教程与总结

一、         概述

为了解决在线无广告播放youku网上的视频。(youku把每个视频切换成若干个小视频)。

视频资源解析可以从网上获取,此网站根据你输入的优酷的播放网页地址解析成若干个真实的视频地址。

二、         实现

首先搜索关闭网络播放器(流媒体播放器的实现方法)

得出的结论,目前主流的播放器分三大阵营微软,苹果,基于FFmpeg内核的。所以我决定从ffmpeg开源的播放器入手。

最出名的ffmpeg播放器vcl播放器,开源免费。最后选择放弃。

原因

1 依赖于vcl的68M的plugins和libvlccore.dll,libvlc.dll项目生成文件过大。

2即使这样不能解决播放多段视频卡顿现象。

最后决定使用ffmpeg官方的ffpaly播放器只有1000多行 (很激动),使用ffmpeg编解码,使用sdl做显示。本想只修改下就行了。结果发现里面代码结构过于复杂,搞懂每行很是吃力。而且是用sdl做显示,sdl需要句柄。而我这个是为wpf项目量身定做的。Wpf只有顶层窗口有句柄。如果是使用wpf嵌入winform控件。导致此winform控件只能最上层显示(原因是wpf是directui思想实现的)。所以也放弃了。

决定使用ffmpeg库,自己开发

查看网上关于ffmpeg开发的总结。对ffmpeg开发有个总体方向。

首先我们先把视频搞出来,参考

网上  100行代码搞定视频。

然后100行搞定音频

网上 

这样视频音频都已经搞出来了。但是我们怎么把视频音频一起搞出来呢?

Csdn有一份文档

网上

此文档介绍了用ffmpeg开发视频播放器的详细方法,有注解。但是已经过时了。最新的代码在网上

但是文档中的思想还是挺受用的。代码不同,思想是通的。

结论,视频包含视频流,音频流,字幕流(一般没有),

音视频同步跟进播放时间戳pts来做的。 视频和音频得出pts的方式有所不同。具体看文档。

如果按文档的注释,然后根据github的代码,编译我们发现视频可以显示,音频出现乌拉乌拉的杂音。 此时我参考100行搞定音频网上

源码修改了github的音频部分。调试运行,可以播放了。

至此 我们的视频播放器可以播放了 ,使用sdl做显示。那现在我们还是没解决问题。网络播放器,多段无卡顿。

在此基础上我们分析,可以开辟一个线程从网络上下载视频,音频,放入到缓冲队列。音视频播放线程从缓冲区读取数据解析。

这就是网络播放器的原理,而且不会卡顿。其中音视频同步用音频驱动视频的方式实现。显示目前暂用sdl。

经过上面这些,我们的网络播放器终于可以工作了。那现在只剩下一个wpf句柄问题了。

好在我看到了网上

文章里面介绍了vlc播放器c#开源代码,可以使用共享内存。但是说的不够详细

这两个开源项目都是用共享内存实现的。 参考此两篇文章。我的播放器终于可以播放网络的视频,音频,然后才wpf播放了。

中间有wpf调用c方法的一些细节。

至此我们的问题真的解决了吗?

NO,因为我们回调函数调用共享内存显示,里面有很多问题,比如当我们关闭程序时会出现访问锁定内存等问题。此问题肯定是可以解决的。但是我们东拼西凑把问题解决了。 当此方案不是最好的。

这3篇文章介绍了怎么使用mediaelement完美解决播放视频问题。

播放器源码可以用网上

下面是我的播放器c部分的代码

/*

本播放器主要是解决 从优酷上播放视频。 是有多段网络视频组成一个完整视频。

解决方案,开辟两个线程,一个线程从网络中读取数据包放入缓冲池(视频缓冲池和音频缓冲池)

一个线程从音频缓冲池读取数据播放。一个从视频缓冲池中读取播放.

难点1:av_read_frame是读取packet(包) 数据, 几包数据 组成avframe(帧)

音频帧转换成byte[] 存储起来 放入缓冲池 吃音频byte[]可以直接放入音频流中播放

视频帧也是byte[]  存储起来,此视频byte[]数组可以转换为图片 PIX_FMT_RGB24

为了同步音视频,我们把没帧的最后一包的pts记录下来放入缓冲区

*/

#include "stdafx.h"

#include "BonkerPlayer.h"

#include <stdio.h>

#include <stdlib.h>

#include <string.h>

extern "C"

{

#include "libavcodec/avcodec.h"

#include "libavformat/avformat.h"

#include "libswresample/swresample.h"

#include "libswscale/swscale.h"

#include <libavutil/avstring.h>

    //SDL

#include "sdl/SDL.h"

#include "sdl/SDL_thread.h"

};

#define VideoBufferMaxSize 80//2048  //视频缓冲区最大值,大于此值 则不下载数据

#define VideoBufferMinSize 20//1024  //视频缓冲区最小值,小于此值,则唤醒下载

#define AudioBufferMaxSize 80//2048  //音频缓冲区最大值,大于此值 则不下载数据

#define AudioBufferMinSize 20//1024  //音频缓冲区最小值,小于此值,则唤醒下载

#define SDL_AUDIO_BUFFER_SIZE 1024 //音频流的缓冲区

//#define VideoType PIX_FMT_YUV420P //视频转换的格式

#define VideoType PIX_FMT_BGR24 //视频转换的格式

#define MAX_AUDIO_FRAME_SIZE 192000 // 1 second of 48khz 32bit audio 

//static char ErrorMsg[100]="";//错误的提示信息

int FileDuration=0;//视频的长度  单位秒

int flag=100;//标识 播放,暂停,退出 0退出,1标识,2暂停

//声明了函数指针

DispalyVideoDele Fn=NULL;

Uint32  audio_len;

Uint8  *audio_pos;

double currentAudioClock=0;//当前音频播放时间

double currentVideoClock=0;//当前视频播放时间

double currentBufferClock=0;//当前以缓冲的时间,用于缓冲进度条

//double currentPlayClock=0;//当前播放的时间,用于播放进度条

double diffClock=0.2;//音视频相差的死区

int CurrentVolume=SDL_MIX_MAXVOLUME/2;//当前声音的大小

SDL_Thread *decodeTid=NULL;//解码线程

SDL_Thread *PlayVideoTid=NULL;//视频播放线程

SDL_Thread *PlayAudioTid=NULL;//音频播放线程

//快进的参数

bool isSeek=false;//是否在快进

int global_seek_index=0;//文件索引 快进

double globle_seek_pos=0;//快进的地方

//存储音频的队列

typedef struct AudioItem

{

    Uint8 *AudioData;//音频数据

    int Length;//音频长度

    double Pts;//时间戳

    AudioItem *Next;//尾部

    SDL_AudioSpec *wanted_spec;

}AudioQueueItem;

typedef struct

{

    AudioQueueItem *FirstItem;//队列头

    AudioQueueItem *LastItem;//队列位

    int Length;//队列长度

    SDL_mutex *audioMutex;//用于同步两个线程同时操作队列的 互斥量

    SDL_cond *audioCond;//唤醒线程

}AudioQueue;

//存储视频的队列

typedef struct VideoItem

{

    Uint8 *VideoData;//音频数据

    int Width;//视频图片的宽度

    int Height;//视频图片的高度

    int Length;//视频长度

    double Pts;//时间戳

    VideoItem *Next;//尾部

}VideoQueueItem;

typedef struct

{

    VideoQueueItem *FirstItem;//队列头

    VideoQueueItem *LastItem;//队列位

    int Length;//队列长度

    double BufferPts;//缓冲的pts

    SDL_mutex *videoMutex;//用于同步两个线程同时操作队列的 互斥量

    SDL_cond *videoCond;//唤醒线程

}VideoQueue;

VideoQueue *videoQueue=NULL;//视频队列

AudioQueue *audioQueue=NULL;//音频队列

//清空视频队列

void VideoQueueClear(VideoQueue *vq)

{

    VideoItem *item,*temp;

    SDL_LockMutex(vq->videoMutex);

    for (item=vq->FirstItem; item!=NULL; item=temp)

    {

        temp=item->Next;//

        av_free(item->VideoData);//释放video里面的数据

        av_free(item);

        vq->Length--;

    }

    vq->FirstItem=NULL;

    vq->LastItem=NULL;

    SDL_UnlockMutex(vq->videoMutex);

}

//清空音频队列

void AudioQueueClear(AudioQueue *aq)

{

    AudioItem *item,*temp;

    SDL_LockMutex(aq->audioMutex);

    for (item=aq->FirstItem; item!=NULL; item=temp)

    {

        temp=item->Next;//

        av_free(item->AudioData);//释放video里面的数据

        av_free(item->wanted_spec);

        av_free(item);

        aq->Length--;

    }

    aq->FirstItem=NULL;

    aq->LastItem=NULL;

    SDL_UnlockMutex(aq->audioMutex);

}

//初始化视频队列

void VideoQueueInit(VideoQueue *vq)

{

    memset(vq, 0, sizeof(VideoQueue));//初始化首地址为0

    vq->videoMutex=SDL_CreateMutex();

    vq->videoCond=SDL_CreateCond();

}

//初始化音频队列

void AudioQueueInit(AudioQueue *aq)

{

    memset(aq,0,sizeof(AudioQueue));

    aq->audioMutex=SDL_CreateMutex();

    aq->audioCond=SDL_CreateCond();

}

//向队列添加数据

int VideoQueuePut(VideoQueue *vq,VideoQueueItem *item)

{

    int result=0;

    SDL_LockMutex(vq->videoMutex);//加锁

    if(vq->Length<VideoBufferMaxSize)

    {

        if(!vq->FirstItem)//第一个item为null 则队列是空的

        {

            vq->FirstItem=item;

            vq->LastItem=item;

            vq->Length=1;

            vq->BufferPts=item->Pts;

        }

        else

        {

            vq->LastItem->Next=item;//添加到队列后面

            vq->Length++;

            vq->LastItem=item;//此item变成队列尾部

            vq->BufferPts=item->Pts;

        }

        if(vq->Length>=VideoBufferMinSize)

        {

            SDL_CondSignal(vq->videoCond);//唤醒其他线程  如果缓冲区里面有几个数据后再唤醒 较好

        }

        result=1;

    }

    else

    {

        SDL_CondWait(vq->videoCond,vq->videoMutex);//解锁  等待被唤醒

    }

    SDL_UnlockMutex(vq->videoMutex);//解锁

    return result;

}

//向队列中取出数据,放入item中

int  VideoQueueGet(VideoQueue *vq,VideoQueueItem *item)

{

    int result=0;

    SDL_LockMutex(vq->videoMutex);

    if(vq->Length>0)

    {

        if(vq->FirstItem)//有数据

        {

            *item=*(vq->FirstItem);

            if(!vq->FirstItem->Next)//只有一个

            {

                vq->FirstItem=NULL;

                vq->LastItem=NULL;

            }else

            {

                vq->FirstItem=vq->FirstItem->Next;

            }

            vq->Length--;

            item->Next=NULL;

            result= 1;

        }

        if(vq->Length<=VideoBufferMinSize)

        {

            SDL_CondSignal(vq->videoCond);//唤醒下载线程

        }

    }

    else

    {

        SDL_CondWait(vq->videoCond,vq->videoMutex);//解锁  等待被唤醒

    }

    SDL_UnlockMutex(vq->videoMutex);

    return result;

}

//向队列添加数据

int AudioQueuePut(AudioQueue *aq,AudioQueueItem *item)

{

    int result=0;

    SDL_LockMutex(aq->audioMutex);//加锁

    if(aq->Length<AudioBufferMaxSize)

    {

        if(!aq->FirstItem)//第一个item为null 则队列是空的

        {

            aq->FirstItem=item;

            aq->LastItem=item;

            aq->Length=1;

        }

        else

        {

            aq->LastItem->Next=item;//添加到队列后面

            aq->Length++;

            aq->LastItem=item;//此item变成队列尾部

        }

        if(aq->Length>=AudioBufferMinSize)

        {

            SDL_CondSignal(aq->audioCond);//唤醒其他线程  如果缓冲区里面有几个数据后再唤醒 较好

        }

        result=1;

    }

    else///音频缓冲区的大小 大于设定值 则让线程等待

    {

        SDL_CondWait(aq->audioCond,aq->audioMutex);//解锁  等待被唤醒

    }

    SDL_UnlockMutex(aq->audioMutex);//解锁

    return result;

}

//向队列中取出数据,放入item中

int AudioQueueGet(AudioQueue *aq,AudioQueueItem *item)

{

    int result=0;

    SDL_LockMutex(aq->audioMutex);

    if(aq->Length>0)

    {

        if(aq->FirstItem)//有数据

        {

            *item=*(aq->FirstItem);

            if(!aq->FirstItem->Next)//只有一个

            {

                aq->FirstItem=NULL;

                aq->LastItem=NULL;

            }else

            {

                aq->FirstItem=aq->FirstItem->Next;

            }

            aq->Length--;

            item->Next=NULL;

            result=1;

        }

        if(aq->Length<=AudioBufferMinSize)

        {

            SDL_CondSignal(aq->audioCond);//唤醒下载线程

        }

    }else

    {

        SDL_CondWait(aq->audioCond,aq->audioMutex);//解锁  等待被唤醒

    }

    SDL_UnlockMutex(aq->audioMutex);

    return result;

}

//输出声音的回调函数

void AudioCallback(void *udata,Uint8 *stream,int len)

{  

    //SDL 2.0

    SDL_memset(stream, 0, len);

    if(audio_len==0)        /*  Only  play  if  we  have  data  left  */

        return;

    len=(len>audio_len?audio_len:len);   /*  Mix  as  much  data  as  possible  */

    SDL_MixAudio(stream,audio_pos,len,CurrentVolume);

    audio_pos += len;

    audio_len -= len;

}  

//下载视频和音频流并 解码  并放入相应的队列中

int DecodePacket(void *arg)

{  

    VideoState *vs=(VideoState *)arg;

    int length=vs->Length;

    double currentAllFilePts=0;

    av_register_all();  //注册所有解码器

    avformat_network_init();  //初始化流媒体格式   

    for (int j = 0; j < length; j++)

    {

        double currentFilePts=0;

        char* url=vs->Urls[j];

        AVFormatContext *pFormatCtx;

        pFormatCtx = avformat_alloc_context(); 

        //打卡文件

        if(avformat_open_input(&pFormatCtx,url,NULL,NULL)!=0)

        { 

            //strcpy(ErrorMsg,"无法打开网络流");

            return -1;

        }

        //avformat_close_input

        if(av_find_stream_info(pFormatCtx)<0) 

        { 

            //strcpy(ErrorMsg,"无法获取流信息"); 

            return -1; 

        }

        //获取此视频的总时间 微妙转化为妙

        //FileDuration+= pFormatCtx->duration/1000000;

        //把一个文件拆分为视频流和音频流

        int videoIndex=-1,audioIndex=-1; 

        int i=0;

        //获取音频流和视频流的索引

        for(i=0; i<pFormatCtx->nb_streams; i++)  

        {

            if(pFormatCtx->streams[i]->codec->codec_type==AVMEDIA_TYPE_VIDEO) 

            { 

                videoIndex=i; 

            } 

            else if(pFormatCtx->streams[i]->codec->codec_type==AVMEDIA_TYPE_AUDIO)

            {

                audioIndex=i;

            }

        }

        AVCodecContext *pCodecCtx,*aCodecCtx;//视频,音频的解码器上下文

        AVCodec *pCodec,*aCodec;//视频,音频解码器

        if(videoIndex!=-1)

        {

            //视频解码器上下文,

            pCodecCtx=pFormatCtx->streams[videoIndex]->codec; 

            pCodec=avcodec_find_decoder(pCodecCtx->codec_id);

        }

        else

        {

        }

        if(audioIndex!=-1)

        {

            //音频解码器上下文

            aCodecCtx=pFormatCtx->streams[audioIndex]->codec; 

            aCodec=avcodec_find_decoder(aCodecCtx->codec_id);

        }

        else

        {

        }

        if(videoIndex!=-1)

        {

            //打开解码器

            if(avcodec_open2(pCodecCtx, pCodec,NULL)<0) 

            { 

                //strcpy(ErrorMsg,"无法打开视频解码器"); 

                return -1; 

            } 

        }

        else

        {

        }

        if(audioIndex!=-1)

        {

            if(avcodec_open2(aCodecCtx, aCodec,NULL)<0) 

            { 

                //strcpy(ErrorMsg,"无法打开音频解码器"); 

                return -1; 

            }  

        }

        else

        {

        }

        AVPacket *packet=(AVPacket *)av_mallocz(sizeof(AVPacket)); 

        AVFrame *pFrame=avcodec_alloc_frame();

        AVFrame *pFrameRGB=avcodec_alloc_frame();

        int frameFinished=0;//是否凑成一帧数据

        int result=0;//标识一个视频是否解码完毕

        int audioLength=0;//音频数组的长度

        int videoLength=0;//视频数组的长度

        //把视频帧转化为数组参数

        struct SwsContext *img_convert_ctx;

        if(videoIndex!=-1)

        {

            videoLength=avpicture_get_size(VideoType, pCodecCtx->width, pCodecCtx->height);

            img_convert_ctx = sws_getContext(pCodecCtx->width, pCodecCtx->height, pCodecCtx->pix_fmt, pCodecCtx->width, pCodecCtx->height, VideoType, SWS_BICUBIC, NULL, NULL, NULL);

        }

        //把音频帧转化为数组的参数

        //uint64_t out_channel_layout=AV_CH_LAYOUT_STEREO; 

        AVSampleFormat out_sample_fmt=AV_SAMPLE_FMT_S16;

        int out_sample_rate=44100;

        int64_t in_channel_layout=av_get_channel_layout_nb_channels(aCodecCtx->channels);

        int out_channels=av_get_channel_layout_nb_channels(aCodecCtx->channels);

        int out_nb_samples=1024;

        audioLength=av_samples_get_buffer_size(NULL,out_channels ,out_nb_samples,out_sample_fmt, 1);

        struct SwrContext *au_convert_ctx; 

        au_convert_ctx = swr_alloc(); 

        au_convert_ctx=swr_alloc_set_opts(au_convert_ctx,aCodecCtx->channels, out_sample_fmt, out_sample_rate, 

            in_channel_layout,aCodecCtx->sample_fmt , aCodecCtx->sample_rate,0, NULL); 

        swr_init(au_convert_ctx); 

        int sample=SDL_AUDIO_BUFFER_SIZE;

        //解码一包数据,一帧数据有多包

        while(flag!=0&&av_read_frame(pFormatCtx, packet)>=0) 

        {

            if(isSeek)//要快进

            {

                //做快进

                if(j==global_seek_index)

                {

                    int seekFlag=avformat_seek_file(pFormatCtx, -1, (globle_seek_pos-10)* AV_TIME_BASE, globle_seek_pos * AV_TIME_BASE, (globle_seek_pos+10)* AV_TIME_BASE, AVSEEK_FLAG_ANY);

                    if(seekFlag>=0)

                    {

                        currentAllFilePts=0;

                        for (int k = 0; k < j; k++)

                        {

                            currentAllFilePts+=vs->times[k];

                        }

                    }

                    //av_seek_frame(pFormatCtx, -1 , globle_seek_pos * AV_TIME_BASE, AVSEEK_FLAG_ANY);

                    isSeek=false;

                }else

                {

                    j=global_seek_index-1;

                    break;

                }

            }

            if(flag==0)//退出

            {

                break;

            }else if(flag==1)//播放

            {

            }else if(flag==2)

            {

                SDL_Delay(1);

                continue;

            }

            frameFinished=0;

            //视频数据包 添加到视频队列中

            if(packet->stream_index==videoIndex) 

            { 

                //把数据包转换为数据帧

                result=avcodec_decode_video2(pCodecCtx,pFrame,&frameFinished,packet);

                double pts=0;

                if(packet->dts == AV_NOPTS_VALUE

                    && pFrame->opaque && *(uint64_t*)pFrame->opaque != AV_NOPTS_VALUE) {

                        pts = *(uint64_t *)pFrame->opaque;

                } else if(packet->dts != AV_NOPTS_VALUE) {

                    pts = packet->dts;

                } else {

                    pts = 0;

                }

                pts *= av_q2d(pFormatCtx->streams[videoIndex]->time_base);

                //printf("+readVideo %d\n",videoQueue->Length);

                if(result<0)//一个视频解码结束了

                {

                    break;//跳出循环,继续解码下一个视频

                }

                if(frameFinished)//解析成了一帧数据,转化为字节数组存放队列中

                {

                    uint8_t *bufferRGB=(uint8_t *)av_mallocz(videoLength);

                    avpicture_fill((AVPicture *)pFrameRGB, bufferRGB, VideoType, pCodecCtx->width, pCodecCtx->height);

                    sws_scale(img_convert_ctx, (const uint8_t* const*)pFrame->data, pFrame->linesize, 0, pCodecCtx->height, pFrameRGB->data, pFrameRGB->linesize);

                    //创建视频item

                    VideoQueueItem *videoItem;

                    videoItem=(VideoQueueItem *)av_mallocz(sizeof(VideoQueueItem));

                    videoItem->Height=pCodecCtx->height;

                    videoItem->Width=pCodecCtx->width;

                    videoItem->VideoData=bufferRGB;

                    //videoItem->Length=videoLength;

                    //videoItem->VideoData=pFrameRGB->data[0];

                    videoItem->Length=pFrameRGB->linesize[0];

                    //获取显示时间戳pts

                    currentFilePts=pts;

                    videoItem->Pts = currentAllFilePts+currentFilePts;//音频绝对pts;

                    videoItem->Next=NULL;

                    //添加到队列中

                    while(flag!=0&&!VideoQueuePut(videoQueue,videoItem));

                    //av_free(bufferRGB);//释放

                }

            }//音频数据包 ,添加到音频队列中

            else if(packet->stream_index==audioIndex)

            {

                result= avcodec_decode_audio4( aCodecCtx, pFrame,&frameFinished, packet);

                double pts=0;

                if(packet->dts == AV_NOPTS_VALUE

                    && pFrame->opaque && *(uint64_t*)pFrame->opaque != AV_NOPTS_VALUE) {

                        pts = *(uint64_t *)pFrame->opaque;

                } else if(packet->dts != AV_NOPTS_VALUE) {

                    pts = packet->dts;

                } else {

                    pts = 0;

                }

                pts *= av_q2d(pFormatCtx->streams[videoIndex]->time_base);

                //printf("+readAudio %d\n",audioQueue->Length);

                if(result<0)//一个视频解码结束了

                {

                    break;//跳出循环,继续解码下一个视频

                }

                if(frameFinished)//解析成了一帧数据,转化为字节数组存放队列中

                {

                    uint8_t *out_buffer=(uint8_t *)av_mallocz(MAX_AUDIO_FRAME_SIZE*2);

                    swr_convert(au_convert_ctx,&out_buffer, MAX_AUDIO_FRAME_SIZE,(const uint8_t **)pFrame->data , pFrame->nb_samples);

                    //创建音频Item

                    AudioItem *audioItem;

                    audioItem=(AudioItem *)av_mallocz(sizeof(AudioItem));

                    audioItem->AudioData=out_buffer;

                    audioItem->Length=audioLength;

                    //获取显示时间戳pts

                    currentFilePts=pts;

                    audioItem->Pts =currentAllFilePts+currentFilePts;//音频绝对pts

                    SDL_AudioSpec *wanted_spec=(SDL_AudioSpec *)av_mallocz(sizeof(SDL_AudioSpec));;  //音频设置

                    //初始化音频设置

                    wanted_spec->silence = 0;  

                    wanted_spec->samples = sample;   

                    wanted_spec->format = AUDIO_S16SYS;   

                    wanted_spec->freq =aCodecCtx->sample_rate;

                    wanted_spec->channels = out_channels;  

                    wanted_spec->userdata = aCodecCtx;

                    wanted_spec->callback = AudioCallback; 

                    if(wanted_spec->samples!=pFrame->nb_samples){ 

                        //SDL_CloseAudio(); 

                        out_nb_samples=pFrame->nb_samples; 

                        audioLength=av_samples_get_buffer_size(NULL,out_channels ,out_nb_samples,out_sample_fmt, 1); 

                        wanted_spec->samples=out_nb_samples; 

                        wanted_spec->freq=aCodecCtx->sample_rate;

                        //SDL_OpenAudio(&wanted_spec, NULL); 

                    } 

                    audioItem->wanted_spec=wanted_spec;

                    //添加到队列中

                    audioItem->Next=NULL;

                    while(flag!=0&&!AudioQueuePut(audioQueue,audioItem));

                }

            }

            av_free_packet(packet);  //释放内存

        }

        av_free(img_convert_ctx);

        av_free(au_convert_ctx);

        av_free(pFrame);

        av_free(pFrameRGB);

        avcodec_close(pCodecCtx);

        avcodec_close(aCodecCtx);

        avformat_close_input(&pFormatCtx);

        currentAllFilePts+=currentFilePts;//把这一段视频地址 累加

        if(flag==0)

        {

            return 1;

        }

    }

    avformat_network_deinit();

    flag=3;//解码结束了

    return 1;

}

//播放视频线程

int PlayVideo(void *arg)

{

    while (flag!=0&&true)

    {

        if(flag==2)// 暂停

        {

            SDL_Delay(1);

            continue;

        }else if(flag==0)//退出

        {

            return -1;

        }else if(flag==1)//播放

        {

        }else if(flag==3)//解码结束了

        {

            //播放结束了

            if(audioQueue->Length<=0&&videoQueue->Length<=0)

            {

                break;

            }

        }

        //视频快于音频 则等待

        if(currentVideoClock>=currentAudioClock+diffClock)

        {

            //音频队中有数据,这样判断是因为,当只有视频时,视频满了,就可以播放了

            if(audioQueue->Length>0&&videoQueue->Length<VideoBufferMaxSize)

            {

                SDL_Delay(1);

                continue;

            }

        }

        VideoItem *videoItem=(VideoItem *)av_mallocz(sizeof(VideoItem));

        //从队列中拿出视频数据

        if(VideoQueueGet( videoQueue,videoItem))

        {

            currentVideoClock=videoItem->Pts;//当前视频时间戳

            if(Fn)

            {

                Fn((unsigned char *)videoItem->VideoData,videoItem->Width,videoItem->Height,videoItem->Pts,videoQueue->BufferPts);

            }

            av_free(videoItem->VideoData);

        }

        av_free(videoItem);

    }

    return 1;

}

//播放音频线程

int PlayAudio(void *arg)

{

    if(SDL_Init( SDL_INIT_AUDIO | SDL_INIT_TIMER)) {   

        printf( "Could not initialize SDL - %s\n", SDL_GetError());  

        return -1; 

    }  

    bool isOpenAudio=false;

    int samples=0;

    SDL_AudioSpec  spec;

    while (true&&flag!=0)

    {

        if(flag==2)// 暂停

        {

            SDL_Delay(1);

            continue;

        }else if(flag==0)//退出

        {

            return -1;

        }else if(flag==1)//播放

        {

        }else if(flag==3)//解码结束了

        {

            //播放结束了

            if(audioQueue->Length<=0&&videoQueue->Length<=0)

            {

                break;

            }

        }

        //音频快于视频 则加锁

        if(currentAudioClock>=currentVideoClock+diffClock)

        {

            if(videoQueue->Length>0&&audioQueue->Length<AudioBufferMaxSize)

            {

                SDL_Delay(1);

                continue;

            }

        }

        AudioItem *audioItem=(AudioItem *)av_mallocz(sizeof(AudioItem));

        //从队列中拿出音频数据

        if( AudioQueueGet( audioQueue,audioItem))

        {

            if(!isOpenAudio)

            {

                SDL_CloseAudio();

                int re=SDL_OpenAudio(audioItem->wanted_spec, &spec);

                samples=audioItem->wanted_spec->samples;

                isOpenAudio=true;

            }

            else

            {

                if(audioItem==NULL)

                {

                    continue;

                }

                if(samples!=audioItem->wanted_spec->samples)

                {

                    SDL_CloseAudio();

                    int re=SDL_OpenAudio(audioItem->wanted_spec, &spec);

                    samples=audioItem->wanted_spec->samples;

                    isOpenAudio=true;

                }

            }

            currentAudioClock=audioItem->Pts;//当前音频时间戳

            audio_pos=audioItem->AudioData;

            audio_len=audioItem->Length;

            SDL_PauseAudio(0);

            while(audio_len>0&&flag!=0)

                SDL_Delay(5);

            av_free(audioItem->AudioData);

            av_free(audioItem->wanted_spec);

        }

        av_free(audioItem);

    }

    SDL_CondSignal(audioQueue->audioCond);//唤醒其他线程  如果缓冲区里面有几个数据后再唤醒 较好

    SDL_CondSignal(videoQueue->videoCond);//唤醒其他线程  如果缓冲区里面有几个数据后再唤醒 较好

    return 1;

}

int _tmain1(VideoState *vs)

{

    currentAudioClock=0;

    currentVideoClock=0;

    currentBufferClock=0;

    //currentPlayClock=0;

    CurrentVolume=SDL_MIX_MAXVOLUME/2;

    if (SDL_Init(SDL_INIT_EVERYTHING) != 0) {

        fprintf(stderr, "Unable to initialize SDL:  %s\n", SDL_GetError());

        return 1;

    }

    atexit(SDL_Quit);

    //atexit(SDL_Quit);// 注册SDL_Quit,当退出时调用,使得退出时程序自动清理

    //flag=2;

    //给音视频队列分配空间

    videoQueue=(VideoQueue *)av_mallocz(sizeof(VideoQueue));

    audioQueue=(AudioQueue *)av_mallocz(sizeof(AudioQueue));

    //初始化音视频队列

    VideoQueueInit(videoQueue);

    AudioQueueInit(audioQueue);

    decodeTid=SDL_CreateThread(DecodePacket,"DecodePacket",vs);

    PlayVideoTid=SDL_CreateThread(PlayVideo,"PlayVideo",NULL);

    PlayAudioTid=SDL_CreateThread(PlayAudio,"PlayAudioTid",NULL);

    return 1;

}

//获取视频的总长度

void InitAllTime(VideoState *vs)

{

    FileDuration=0;

    int length=vs->Length;

    av_register_all();  //注册所有解码器

    avformat_network_init();  //初始化流媒体格式   

    for (int j = 0; j < length; j++)

    {

        char* url=vs->Urls[j];

        AVFormatContext *pFormatCtx = avformat_alloc_context(); 

        //打卡文件

        if(avformat_open_input(&pFormatCtx,url,NULL,NULL)!=0)

        { 

            //strcpy(ErrorMsg,"无法打开网络流");

            return;

        }

        if(av_find_stream_info(pFormatCtx)<0) 

        { 

            //strcpy(ErrorMsg,"无法获取流信息"); 

            return; 

        }

        //保存每个文件的播放长度

        vs->times[j]=pFormatCtx->duration/1000000;

        //获取此视频的总时间 微妙转化为妙

        FileDuration+= vs->times[j];

        avformat_close_input(&pFormatCtx);

    }

    avformat_network_deinit();

}

void bonker_pause()

{

    flag=2;

}

VideoState *_vs;

//获取视频总长度

double bonker_gettime()

{

    FileDuration=0;

    if(_vs!=NULL)

    {

        InitAllTime(_vs);

    }

    return FileDuration;

}

void bonker_open()

{

    if(_vs!=NULL)

    {      

        _tmain1(_vs);

    }else

    {  

    }

}

void bonker_play()

{

    flag=1;

}

void bonker_quit()

{

    bonker_close();

}

void bonker_init(DispalyVideoDele _fn)

{

    FileDuration=0;//视频的长度  单位秒

    flag=2;//标识 播放,暂停,退出 0退出,1标识,2暂停

    audio_len=0;

    currentAudioClock=0;//当前音频播放时间

    currentVideoClock=0;//当前视频播放时间

    currentBufferClock=0;//当前以缓冲的时间,用于缓冲进度条

    //currentPlayClock=0;//当前播放的时间,用于播放进度条

    diffClock=0.2;//音视频相差的死区

    CurrentVolume=SDL_MIX_MAXVOLUME/2;//当前声音的大小

    //快进的参数

    isSeek=false;//是否在快进

    global_seek_index=0;//文件索引 快进

    globle_seek_pos=0;//快进的地方

    Fn=_fn;

}

void bonker_addurl(char *vs)

{

    if(_vs==NULL)

    {

        _vs=(VideoState *)av_mallocz(sizeof(VideoState));

        _vs->Length=0;

    }

    av_strlcpy(_vs->Urls[_vs->Length],vs,UrlLength);

    _vs->Length++;

}

//释放内存资源

void bonker_close()

{

    flag=0;//退出

    SDL_CloseAudio();

    if(videoQueue!=NULL)

    {

        SDL_CondSignal(videoQueue->videoCond);

    }

    if(audioQueue!=NULL)

    {

        SDL_CondSignal(audioQueue->audioCond);

    }

    SDL_Delay(10);

    SDL_WaitThread(PlayVideoTid,NULL);

    SDL_WaitThread(PlayAudioTid,NULL);

    SDL_WaitThread(decodeTid,NULL);

    if(videoQueue!=NULL)

    {

        VideoQueueClear(videoQueue);//释放视频队列

        //videoQueue=NULL;

    }

    if(audioQueue!=NULL)

    {

        AudioQueueClear(audioQueue);

        //audioQueue=NULL;

    }

    SDL_DestroyMutex(audioQueue->audioMutex);

    SDL_DestroyCond(audioQueue->audioCond);

    SDL_DestroyMutex(videoQueue->videoMutex);

    SDL_DestroyCond(videoQueue->videoCond);

    av_free(audioQueue);

    av_free(videoQueue);

    flag=2;

    /*SDL_DetachThread(decodeTid);

    SDL_DetachThread(PlayVideoTid);

    SDL_DetachThread(PlayAudioTid);*/

    if(_vs!=NULL)

    {

        av_free(_vs);

    }

    _vs=NULL;

    SDL_Quit();

    //关闭解码线程,视频播放线程,音频播放线程

    /*if(decodeTid!=NULL)

    {

    decodeTid=NULL;

    }

    if(PlayVideoTid!=NULL)

    {

    PlayVideoTid=NULL;

    }

    if(PlayAudioTid!=NULL)

    {

    PlayAudioTid=NULL;

    }*/

}

//设置声音

void bonker_set_volumn(int volume)

{

    if(volume>=0&&volume<=128)

    {

        CurrentVolume=volume;

    }

}

//获取音量

int bonker_get_volume()

{

    return CurrentVolume;

}

//快进 快退

void bonker_seek(double seek_pos)

{

    bool flagTemp=flag;

    flag=2;

    SDL_Delay(50);

    //当快进的时间在缓冲区内

    if(seek_pos>=currentVideoClock&&seek_pos<=videoQueue->BufferPts)

    {

        //清空之前的音频

        AudioItem *item,*temp;

        SDL_LockMutex(audioQueue->audioMutex);

        for (item=audioQueue->FirstItem; item!=NULL; item=temp)

        {

            temp=item->Next;//

            av_free(item->AudioData);//释放video里面的数据

            av_free(item->wanted_spec);

            av_free(item);

            audioQueue->Length--;

            if(temp!=NULL)

            {

                if(temp->Pts>=seek_pos)//目前缓冲区,大于此跳转位置

                {

                    break;

                }

            }else

            {

                audioQueue->FirstItem=NULL;

                audioQueue->LastItem=NULL;

            }

        }

        SDL_UnlockMutex(audioQueue->audioMutex);

        //清空之前的视频

        VideoItem *item1,*temp1;

        SDL_LockMutex(videoQueue->videoMutex);

        for (item1=videoQueue->FirstItem; item1!=NULL; item1=temp1)

        {

            temp1=item1->Next;//

            av_free(item1->VideoData);//释放video里面的数据

            av_free(item1);

            videoQueue->Length--;

            if(temp1!=NULL)

            {

                if(temp1->Pts>=seek_pos)//目前缓冲区,大于此跳转位置

                {

                    break;

                }

            }

            else

            {

                videoQueue->FirstItem=NULL;

                videoQueue->LastItem=NULL;

            }

        }

        SDL_UnlockMutex(videoQueue->videoMutex);

    }

    else if(seek_pos>=0&&seek_pos<=FileDuration)//用av_seek_file,计算那个文件,

    {

        double pos=0;

        for (int i = 0; i < _vs->Length; i++)

        {

            pos+=_vs->times[i];

            if(pos>seek_pos)//就在这个文件内

            {

                isSeek=true;

                global_seek_index=i;

                globle_seek_pos=seek_pos- pos+_vs->times[i];//此文件快进到的位置

                break;

            }

        }

        //清空缓冲区

        VideoQueueClear(videoQueue);

        AudioQueueClear(audioQueue);

    }

    flag=flagTemp;

    SDL_CondSignal(audioQueue->audioCond);//唤醒其他线程  如果缓冲区里面有几个数据后再唤醒 较好

    SDL_CondSignal(videoQueue->videoCond);//唤醒其他线程  如果缓冲区里面有几个数据后再唤醒 较好

}

更多相关推荐:
GMP工作总结

GMP中心思想质量是生产出来的生产过程中必须抓质量以质量为工作中心没有记录就等于没有发生同样没有质量就等于没有生产没有质量造成浪费损失基于这样一个指导思想没有真实记录没有质量即可以取消考勤非人为控制的质量除外G...

GMP总结

GMP实务学习总结这学期学校为加强我们对制药行业的了解给我们安排了GMP实务的课程,并有幸请来了百特公司的老师给我们培训。老师给我们带来的不仅仅是对gmp的理解,更是对实际生产中的一种思维方式的改变。改变了一些…

GMP工作总结

20xx年GMP工作总结GMP中心思想:质量是生产出来的,生产过程中必须抓质量,以质量为工作中心。没有记录就等于没有发生,同样,没有质量就等于没有生产,没有质量造成浪费、损失。基于这样一个指导思想,没有真实记录…

20xxGMP车间工作总结及20xx年工作展望

GMP车间20xx年工作总结及20xx年工作展望今年以来,GMP车间在公司领导的正确领导下,以创新管理为契机,在全面遵循各项规章制度的要求下,扎实落实公司各项任务。按照机制创新、管理创新、方法创新的新思路,带领…

GMP认证总结

一、质量保证体系情况检查要点1:质量体系的概念是否建立质量体系的概念:该体系应当涵盖影响药品质量的所有因素,包括确保药品质量符合预定用途的有组织、有计划的全部活动。质量体系与质量保证、质量控制、GMP、之间的关…

新版GMP培训心得体会

一、新版GMP的特点新版GMP实施一年多以来,大家经过多次培训学习和现场检查,都感觉到新版GMP认证要求比xx年版明显提高,难度加大。新旧版对比,变化的体现有5个特点:最主要变化是软件,增加了很多内容。一是强化…

GMP培训总结

20xx年GMP培训总结一总述20xx年4月19日20xx年4月22日公司派我到聊城参加省局组织的药品GMP培训班经过这次培训第一我知道了质量风险管理的本质所在及怎样对岗位设备系统进行风险分析第二我了解了偏差管...

GMP办工作总结

20xx年对于公司是极具历史意义的一年,回首20xx年,公司为明年的GMP认证做了大量的准备工作,在符总的直接领导下,通过全体同仁精诚协力、不断进取,为企业求生存、求发展作出了非常多的艰辛努力和探索。GMP工作…

新版GMP认证心得

20xx年x月份我公司顺利的通过了GMP专家组的认证,取得了“GMP战役”的胜利,在过去的1年多时间里,公司的每一个人都在积极的为GMP认证做准备工作,从小到大,事无巨细,点点滴滴。研发部作为一个企业的重中之重…

新版GMP认证总结

20xx版GMP认证总结一、质量保证体系情况检查要点1:质量体系的概念是否建立质量体系的概念:该体系应当涵盖影响药品质量的所有因素,包括确保药品质量符合预定用途的有组织、有计划的全部活动。质量体系与质量保证、质…

某企业GMP复认证工作小结

GMP复认证工作小结20xx年x月x日-20日,GMP专家组一行4人到我厂进行复查认证。对公司各个部门进行了检查。我陪同参加了对固体制剂车间的检查,检查员来到车间更衣进入后,因为三位检查员都是女性,为了能全面…

GMP认证工作体会

云南康恩贝植物药有限公司当今时代,竞争愈来愈激烈,产品质量是每个制药企业恪守的、苦心经营的竞争法宝,而新版GMP认证是全面提升质量管理在制药行业的体现。公司于20xx年x月顺利通过了国家新版GMP认证,在实施…

gmp总结(6篇)