ffmpeg播放器实战(播放渲染类)

简介: 播放渲染类

1.传入播放信息类

Render::Render(DataInfo* dataInfo,QObject *parent)
    : QObject{parent}
{
    this->dataInfo = dataInfo;
}

2.开启渲染

void Render::slotRender()
{
    auto  videoCodecCtx = dataInfo->getVideoCtx();
    auto  audioCodecCtx = dataInfo->getAudioCtx();
    int   SDLFormat     = dataInfo->getSDLFmt();
    int   audioIndex    = dataInfo->getAudioIndex();
    float speed         = (dataInfo->getPlaySpeed() == PLAY_SLOW) ? 0.5 : dataInfo->getPlaySpeed();
    int   isOpenAudio   = -1;
    if (audioIndex >= 0) {
        //音频
        //SDL_AudioSpec是Simple DirectMedia Layer(SDL)库中的一个结构体,用于指定音频设备的参数和配置。
        //它包含了音频流的格式、采样率、声道数等信息,以及回调函数等。
        //通过设置SDL_AudioSpec的各个字段,可以实现音频的播放、录制和处理等功能。
        SDL_AudioSpec audioSpec;
        audioSpec.freq     = audioCodecCtx->sample_rate;            //采样率
        audioSpec.format   = SDLFormat;                             //声音格式
        audioSpec.channels = audioCodecCtx->ch_layout.nb_channels;  //通道数
        audioSpec.silence  = 0;                                     //静音值
        audioSpec.samples  = audioCodecCtx->frame_size;             //每帧大小
        audioSpec.callback = playAudioCallback;                     //回调函数
        //audioSpec.userdata 是一个用于存储音频特殊数据的字段。
        //它允许开发者在音频规格对象中附加自定义数据,以便在需要时进行访问和处理。
        //这个字段的具体用途和内容取决于使用该字段的应用程序或平台。
        //在处理音频时,开发者可以根据自己的需求将相关的信息存储在 userdata 中,以便后续处理过程中使用。
        audioSpec.userdata = dataInfo;
        //SDL_OpenAudio是一个用于初始化音频子系统的SDL库函数。它用于设置音频设备的参数,并打开音频流以进行读写操作。通过调用此函数,可以配置音频格式、采样率、声道数和缓冲区大小等参数。
        isOpenAudio        = SDL_OpenAudio(&audioSpec, nullptr);
        if (speed == 1)
        //SDL_PauseAudio函数是一个IT类问题。这个函数用于暂停或恢复音频流的播放。如果参数为0,表示恢复播放;如果参数为1,表示暂停播放。
            SDL_PauseAudio(0);
    }
    //视频
    视频
    //SDL_CreateRenderer函数是SDL库中用于创建渲染器的函数。它的原型定义如下:
    //SDL_Renderer* SDL_CreateRenderer(SDL_Window* window, int index, Uint32 flags)
    //参数解释:
    //window:要创建渲染器的窗口指针。
    //index:指定渲染的驱动索引,一般传入-1表示使用第一个支持的驱动。
    //flags:渲染器的附加选项,可以是SDL_RendererFlags枚举类型的值,一般传入0即可。
    //函数返回一个SDL_Renderer指针,如果创建失败则返回NULL。
    //渲染器用于将图形数据绘制到窗口上,可以通过渲染器实现图形、纹理、字体等的渲染操作。创建渲染器后,可以使用SDL_RenderPresent函数将绘制结果更新到窗口上。
    //SDL_RENDERER_ACCELERATE是SDL库中的一个常量,用于指定渲染器的加速类型。它表示使用硬件加速来执行渲染操作,以提高性能和效率
    SDL_Renderer* SDLRend = SDL_CreateRenderer(dataInfo->getSDLWind(), -1, SDL_RENDERER_ACCELERATED);
    if (!SDLRend) {
        qDebug() << "SDL_CreateRenderer fail";
    }
    SDL_CreateTexture函数是SDL库中的一个函数,用于创建一个纹理对象。纹理对象可以用来在窗口上绘制图像。
    //该函数的原型如下: SDL_Texture* SDL_CreateTexture(SDL_Renderer* renderer, Uint32 format, int access, int w, int h)
    //参数说明:
    //renderer:渲染器对象,用于创建纹理的目标渲染器。
    //format:纹理的像素格式,可以参考SDL_PixelFormatEnum枚举类型。
    //access:纹理的访问方式,可以是SDL_TEXTUREACCESS_STATIC、SDL_TEXTUREACCESS_STREAMING或SDL_TEXTUREACCESS_TARGET。
    //w和h:纹理的宽度和高度。
    //函数返回值为创建成功的纹理对象指针,如果创建失败则返回NULL。
    //注意:在使用完纹理后,需要使用SDL_DestroyTexture函数来销毁纹理对象,以释放内存空间。

    //这是一个关于SDL库中的像素格式的问题。SDL_PIXELFORMAT_IYUV是SDL库中的一种像素格式,它用于表示色彩空间转换中的YUV图像数据。其中,Y表示亮度(luma),U和V表示色度(chroma)。IYUV格式在视频编解码和处理中经常使用。
    //SDL_TEXTUREACCESS_STREAMING是SDL(Simple DirectMedia Layer)库中的一个常量,用于指定纹理的访问方式。具体来说,当使用SDL创建一个纹理时,可以通过设置纹理的访问方式来决定如何访问纹理的像素数据。
    // SDL_TEXTUREACCESS_STREAMING表示纹理是可通过内存缓冲区进行动态更新的。这意味着可以通过锁定纹理并直接修改像素数据来实现每帧更新纹理的效果。通常,这种访问方式适用于需要频繁更新纹理数据的场景,如实时渲染、视频播放等。
    SDL_Texture* SDLText =
        SDL_CreateTexture(SDLRend, SDL_PIXELFORMAT_IYUV, SDL_TEXTUREACCESS_STREAMING, videoCodecCtx->width, videoCodecCtx->height);
    if (!SDLText) {
        qDebug() << "SDL_CreateTexture fail";
    }
    int64_t lastPtsTime    = 0;
    //av_gettime() 是 FFmpeg 库中的一个函数,用于获取当前系统时间(以微秒为单位)。它通常用于计算时间戳和执行时间相关的操作。该函数的原型如下:
    //int64_t av_gettime(void);
    //函数返回一个 int64_t 类型的整数,表示当前系统的时间。具体的时间单位要根据操作系统的不同而定。在 Windows 上,它通常以100-nanosecond为单位(即10^-7秒),而在其他系统上可能以微秒为单位(即10^-6秒)。
    //请注意,av_gettime() 函数是属于 FFmpeg 库而不是 C 标准库。如果你希望使用该函数,需要先引入 FFmpeg 的相关头文件,并链接对应的库。
    int64_t lastRenderTime = av_gettime();
    int64_t audioClock     = 0;
    //判断是否解码
    //判断视频数据是否为空
    //判断音频数据是否为空
    while (dataInfo->getDecodeState() || !dataInfo->videoIsEmpty() || !dataInfo->audioIsEmpty()) {
        //播放状态为空则跳出循环
        if (dataInfo->getPlayState() == PLAY_STOP) {
            break;
        }
        //视频和音频数据为空
        if (dataInfo->videoIsEmpty() && dataInfo->audioIsEmpty()) {
            //暂停0.005秒
            QThread::msleep(5);
            //跳出本次循环
            continue;
        }
        //如果视频为空
        if (dataInfo->videoIsEmpty()) {
            //暂停0.01秒
            QThread::msleep(10);
            //跳出本次循环
            continue;
        }
        //获取第一个视频数据
        auto videoData = dataInfo->videoPop();
        //如果视频渲染时间设置为-1
        if (videoData.ptsTime == -1)
            continue;
        //获取播放速度
        speed = (dataInfo->getPlaySpeed() == PLAY_SLOW) ? 0.5 : dataInfo->getPlaySpeed();
        //如果跳转为真
        if (dataInfo->getSeekFlag()) {
            dataInfo->setSeekFlag(false);
            //设置跳转时间
            dataInfo->setAudioClock(0);
        } else {
            //最后渲染的pts不等于0和视频pts不等于0
            if (lastPtsTime != 0 && videoData.ptsTime != 0) {
                //视频打开
                //获得音频数据索引
                //播放速度为1
                if (isOpenAudio >= 0 && dataInfo->getAudioIndex() >= 0 && speed == 1) {
                    //SDL_GetAudioStatus函数是SDL库中的一个函数,用于获取音频设备的当前状态。该函数返回一个枚举类型的值,表示音频设备的状态,可能的取值包括:
                    //SDL_AUDIO_STOPPED:音频设备当前停止状态。
                    //SDL_AUDIO_PLAYING:音频设备当前正在播放音频。
                    //SDL_AUDIO_PAUSED:音频设备当前处于暂停状态。
                    //请注意,SDL_GetAudioStatus函数需要在SDL初始化后才能正常调用,并且需要先使用SDL_OpenAudio函数打开音频设备才能获取正确的状态。
                    if (SDL_GetAudioStatus() == SDL_AUDIO_PAUSED){
                        //暂停状态设置为播放状态
                        SDL_PauseAudio(0);
                    }
                    audioClock = dataInfo->getAudioClock();
                    //如果视频播放pts<播放时间
                    if (videoData.ptsTime < audioClock) {
                        //大于半秒
                        if (audioClock - videoData.ptsTime >= 500) {  //视频帧太慢,则丢弃
                            qDebug() << "丢帧:" << videoData.ptsTime;
                            //更新进度
                            emit signalProgress(videoData.ptsTime);
                            //最后pts
                            lastPtsTime = videoData.ptsTime;
                            //释放该帧
                            av_frame_free(&videoData.frame);
                            continue;
                        }
                    } else {
                        while (dataInfo->getPlayState() == PLAY_PLAY && !dataInfo->audioIsEmpty()
                           && videoData.ptsTime > dataInfo->getAudioClock() && !dataInfo->getSeekFlag()) {
                           QThread::msleep(1);
                    }
                }
            } else {
                //音频数据大于0
                if (dataInfo->getAudioIndex() >= 0) {
                    if (SDL_GetAudioStatus() == SDL_AUDIO_PLAYING)
                        //设置暂停
                        SDL_PauseAudio(1);
                    //获得音频第一帧
                    auto audioData = dataInfo->audioFirst();
                    //音频渲染时间<视频渲染时间
                    //音频渲染时间>=0
                    while (audioData.ptsTime >= 0 && audioData.ptsTime < videoData.ptsTime) {
                        //获得音频数据
                        auto tempData = dataInfo->audioPop();
                        //释放
                        av_free(tempData.buff);
                        //获得音频第一个数据
                        audioData = dataInfo->audioFirst();
                        //设置音频时间
                        dataInfo->setAudioClock(audioData.ptsTime);
                    }
                }
                int64_t offset    = (videoData.ptsTime - lastPtsTime) * 1000.0 / speed;
                int64_t nowOffset = av_gettime() - lastRenderTime;
                if (offset < nowOffset) {
                    if (nowOffset - offset >= offset) {  //视频帧太慢,则丢弃
                        qDebug() << "丢帧:" << videoData.ptsTime;
                        emit signalProgress(videoData.ptsTime);
                        lastPtsTime    = videoData.ptsTime;
                        lastRenderTime = av_gettime();
                        av_frame_free(&videoData.frame);
                        continue;
                    }
                    } else {
                        if (offset > nowOffset + 1000)
                            QThread::usleep(offset - nowOffset - 1000);
                    }
                }
            }
        }
        //显示
        //SDL_UpdateYUVTexture是一个SDL函数,用于更新YUV格式的纹理数据。
        //通过传入纹理、矩形区域、YUV帧数据和行大小等参数,
        //函数会将YUV帧数据更新到纹理上,以便渲染器显示。
        SDL_UpdateYUVTexture(SDLText,
            nullptr,
            videoData.frame->data[0],
            videoData.frame->linesize[0],
            videoData.frame->data[1],
            videoData.frame->linesize[1],
            videoData.frame->data[2],
            videoData.frame->linesize[2]);
        //
        SDL_RenderClear(SDLRend);
        //SDL_RenderClear函数是用来清空渲染器的函数,它会将渲染器上的所有内容清除,使得渲染器变为空白。
        //SDL_RenderCopy是SDL库中的一个函数,用于将纹理数据复制到渲染目标上
        //在使用SDL_RenderCopy函数时,需要传入渲染器(renderer)、纹理(texture)、源矩形(srcrect)和目标矩形(dstrect)等参数。该函数会将纹理的一部分或全部复制到目标矩形所指定的位置上。
        //例如,在上述引用的代码中,SDL_RenderCopy函数被用来渲染视频画面。根据旋转角度的不同,可以选择使用SDL_RenderCopy或SDL_RenderCopyEx函数来实现渲染。
        //总结起来,SDL_RenderCopy函数用于将纹理数据复制到渲染目标上,通过传入不同的参数可以实现不同的渲染效果
        SDL_RenderCopy(SDLRend, SDLText, nullptr, nullptr);
        //SDL_RenderPresent函数通过调用SDL_Renderer的RenderPresent方法,将图像显示在屏幕上
        SDL_RenderPresent(SDLRend);
        lastPtsTime    = videoData.ptsTime;
        lastRenderTime = av_gettime();
        //设置播放时间
        dataInfo->setPlayTime(videoData.ptsTime);
        emit signalProgress(videoData.ptsTime);
        //暂停
        while (dataInfo->getPlayState() == PLAY_PAUSE) {
            if (dataInfo->getAudioIndex() >= 0)
                if (SDL_GetAudioStatus() == SDL_AUDIO_PLAYING)
                    SDL_PauseAudio(1);
            QThread::msleep(10);
           //防止暂停时失去焦点就没有显示
            SDL_UpdateYUVTexture(SDLText,
                             nullptr,
                             videoData.frame->data[0],
                             videoData.frame->linesize[0],
                             videoData.frame->data[1],
                             videoData.frame->linesize[1],
                             videoData.frame->data[2],
                             videoData.frame->linesize[2]);
            SDL_RenderClear(SDLRend);
            SDL_RenderCopy(SDLRend, SDLText, nullptr, nullptr);
            SDL_RenderPresent(SDLRend);
        }
        av_frame_free(&videoData.frame);
        if (dataInfo->getAudioIndex() >= 0) {
            if (SDL_GetAudioStatus() == SDL_AUDIO_PAUSED)
                SDL_PauseAudio(0);
        }
    }

    if (dataInfo->getAudioIndex() >= 0)
        SDL_CloseAudio();
    //销毁SDL
    SDL_DestroyTexture(SDLText);
    SDL_DestroyRenderer(SDLRend);

    //暂停播放
    emit signalPlayStop();
}

void Render::playAudioCallback(void* udata, uchar* stream, int len)
{
    //SDL_memset 是 C/C++ 语言中的一个函数,用于将指定内存块的每个字节都设置为特定的值。
    //它的原型如下: void *SDL_memset(void *dst, int value, size_t len);
    //其中,参数 dst 是要设置的内存块的起始地址,value 是要设置的值,
    //len 是要设置的字节数。 例如,如果我们想将一个数组的每个元素都设置为0,
    //可以使用 SDL_memset 函数进行操作: int arr[10]; SDL_memset(arr, 0, sizeof(arr));
    //这样,arr 数组中的每个元素都会被设置为0。
    SDL_memset(stream, 0, len);
    auto dataInfo  = (DataInfo*)udata;
    int  SDLFormat = dataInfo->getSDLFmt();
    auto data      = dataInfo->audioPop();
    if (data.ptsTime > 0) {
        dataInfo->setAudioClock(data.ptsTime);
        if (data.buffSize > 0)
        //SDL_MixAudioFormat是一个用于混合音频数据的函数,它可以在不同的音频格式之间进行混合。它是SDL(Simple DirectMedia Layer)库中提供的一个函数。 函数原型如下: void SDL_MixAudioFormat(Uint8* dst, const Uint8* src, SDL_AudioFormat format, Uint32 len, int volume); void SDL_MixAudioFormat(Uint8* dst, const Uint8* src, SDL_AudioFormat format, Uint32 len, int volume); 参数说明: dst:目标音频数据的指针,它将包含混合后的音频数据。 src:源音频数据的指针,它包含要混合的音频数据。 format:音频数据的格式,使用SDL_AudioFormat类型表示,例如SDL_AudioFormat.SDL_AUDIO_S16LSB表示16位带符号整数的小端存储格式。 len:要混合的音频数据的长度(以字节为单位)。 volume:音频混合的音量控制参数,范围是0到128。 该函数将通过叠加两个音频数据来实现混合,将源音频数据(src)与目标音频数据(dst)进行混合,并将结果存储在目标音频数据中。音量参数(volume)可以用来控制混合后的音量大小。 注意:在调用该函数之前,需要确保源音频数据和目标音频数据的格式是一致的,否则可能会导致不可预期的结果。
        SDL_MixAudioFormat(stream, data.buff, SDLFormat, data.buffSize, dataInfo->getPlayVolume());
        av_free(data.buff);
    }
}

本文福利, 免费领取C++音视频学习资料包+学习路线大纲、技术视频/代码,内容包括(音视频开发,面试题,FFmpeg ,webRTC ,rtmp ,hls ,rtsp ,ffplay ,编解码,推拉流,srs),有需要的可以进企鹅裙927239107领取哦~

猜你喜欢

转载自blog.csdn.net/m0_73443478/article/details/134673227