Ffmpeg+QT简单播放器的设计

目的

有问题请加qq群进行讨论 (音视频高级开发交流群3 群号782508536)

更多FFmpeg知识请点击:FFmpeg/WebRTC/RTMP音视频流媒体高级开发
课程部分演示:

Janus WebRTC SFU服务器开发

零声学院-音视频课程变速播放演示

让读者对以下知识有初步的掌握

  • 理解播放器的基本框架
  • 熟悉常用的结构体
    • AVFormatContext
    • AVCodecContext
    • AVCodec
    • AVFrame
    • AVPacket
    • AVStream
  • 理解基本的同步原理

开发准备

开发环境

  • Windows7
  • QT 5.9.2 + Creator 4.4.1
  • 第三方库
    • FFMPEG 用来读取码流以及解码
    • SDL2 用来显示画面
    • PortAudio 用来播放声音

开发语言

  • C/C++ 该demo主要以C的方式去做开发,但多线程的创建使用了C++11的机制;后续的开发以C++为主

框架

框图如图所示
这里写图片描述

  • 线程划分

    1. 主循环读取数据
    2. 音频线程解码并播放声音
    3. 视频线程解码并显示视频
  • 文件划分

    • main.c 做初始化工作,读取码流,分发码流

    • audio.c 音频解码和声音播放

      • AudioInit 初始化音频
      • AudioClose 释放资源
      • AudioDecodeThread 音频解码和播放线程
      • AudioPlay 播放声音
      • AudioPacketPush 写入未解码的音频包
      • AudioPacketSize 当前音频尚未解码的数据总容量
      • AudioSetTimeBase 音频的base time(以时钟有关系,比如TS为1/90KHZ,另一常见的为1/1000)
    • vidoe.c 视频解码和视频播放

      • VideoInit 初始化视频
      • VideoClose 释放资源
      • VideoDecodeThread 视频解码和播放线程
      • VideoDisplay 显示帧
      • VideoPacketPush 写入未解码的视频包
      • VideoPacketSize 当前视频尚未解码的数据总容量
      • VideoGetFirstFrame 是否已经解出第一帧
      • VideoSetTimeBase 视频的base time
    • avpacket_queue.c 音频、视频队列,存储解码前的数据

      • PacketQueueInit 初始化队列
      • PacketQueueGetSize 队列中所有packet的数据长度(单位为字节)
      • PacketQueuePut 插入元素
      • PacketQueueTake 读取元素
    • log.c 打印日志

      • LogInit 初始化Log模块,启动后将在运行目录生成一个log.txt的文件,每次程序重启都会重新产生一个文件并将原来的文件覆盖。
      • LogDebug、LogInfo、LogError和LogNotice 对应不同级别的打印,但目前只是通过宏是否实现来控制
      • FunEntry 用在函数入口
      • FunExit 用在函数出口
    • clock.c 时钟同步用

      • AVClockResetTime 重置时钟
      • AVClockGetCurTime 获取当前时间
      • AVClockSetTime 设置时间
      • AVClockEnable 使能时钟
      • AVClockDisable 禁用时钟
      • AVClockIsEnable 获取时钟是否使能
  • 开发前需要理清的逻辑

    • 采用什么方法实现多线程(这里使用C++11的Thread)
    • 读取结束时如何处理(通过插入数据为0的数据包通知解码线程)
    • 音视频数据队列的最大buffer 音频+视频最大缓存为15M
    • 同步方式的处理(该工程使用了公共时钟做同步)

该工程的目的

该工程实现了简单的播放器,主要是搭建一个方便QT进行调试的环境,读者可以在QT环境通过Debug的运行方式理解第一章节提到的重要结构体。

代码

下载地址:http://download.csdn.net/download/muyuyuzhong/10272272
必须把dll目录的文件拷贝到编译目录,比如
这里写图片描述

代码预览

audio.c

#include <thread>
#include <portaudio.h>
#include "audio.h"

#include "avpackets_queue.h"
#include "av_clock.h"
#include "video.h"
#include "log.h"

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

const double kPortAudioLatency  = 0.3;  // 0.3秒,也就是说PortAudio库需要累积0.3秒的数据后才真正播放

#define ERR_STREAM	stderr


typedef struct audio_param
{
    AVFrame wantFrame;			// 指定PCM输出格式

    PaStreamParameters *outputParameters;
    PaStream *stream;
    int sampleRate;
    int format;
    PacketQueue audioQueue;     // 音频队列
    int packetEof;              // 数据包已经读取到最后
    int quit;                   // 是否退出线程
    double audioBaseTime;       // 音频base time
    SwrContext	*swrCtx;        // 音频PCM格式转换
}T_AudioParam;

static T_AudioParam sAudioParam;


static int _AudioDecodeFrame( AVCodecContext *pAudioCodecCtx, uint8_t *audioBuf, int bufSize , int *packeEof);


void AudioDecodeThread( void *userdata )
{
    FunEntry();

    AVCodecContext	*pAudioCodecCtx = (AVCodecContext *) userdata;
    int		len1		= 0;
    int		audio_size	= 0;
    uint8_t audioBuf[AVCODE_MAX_AUDIO_FRAME_SIZE];

    AVClockDisable();       // 禁止时钟
    while ( sAudioParam.quit != 1 )
    {
        if(sAudioParam.packetEof && sAudioParam.audioQueue.size == 0)
        {
            sAudioParam.quit = 1;
        }
        audio_size = 0;
        if(VideoGetFirstFrame())            // 等图像出来后再出声音
        {
            audio_size = _AudioDecodeFrame( pAudioCodecCtx, audioBuf, sizeof(audioBuf), &sAudioParam.packetEof);
        }
        if ( audio_size > 0 )
        {
            AudioPlay(audioBuf, audio_size, 0);
        }
        else
        {
            std::this_thread::sleep_for(std::chrono::milliseconds(20));     // 没有数据时先休眠20毫秒
        }
    }


    FunExit();
}


// 对于音频来说,一个packet里面,可能含有多帧(frame)数据
/**
 * @brief _AudioDecodeFrame
 * @param pAudioCodecCtx
 * @param audioBuf
 * @param bufSize
 * @param packeEof
 * @return
 */
int _AudioDecodeFrame( AVCodecContext *pAudioCodecCtx,
                       uint8_t *audioBuf, int bufSize, int *packeEof)
{
    AVPacket	packet;
    AVFrame		*pFrame = NULL;
    int		gotFrame = 0;
    int		decodeLen  = 0;
    long 	audioBufIndex = 0;
    int		convertLength	= 0;
    int		convertAll	= 0;

    if ( PacketQueueTake( &sAudioParam.audioQueue, &packet, 1 ) < 0 )
    {
        std::this_thread::sleep_for(std::chrono::milliseconds(30));     // 没有取到数据休眠
        return(-1);
    }

    pFrame = av_frame_alloc();
    *packeEof = packet.size ? 0: 1;       // 约定使用数据长度为0时来标记packet已经读取完毕

    while ( packet.size > 0 )
    {
        /*
         * pAudioCodecCtx:解码器信息
         * pFrame:输出,存数据到frame
         * gotFrame:输出。0代表有frame取了,不意味发生了错误。
         * packet:输入,取数据解码。
         */
        decodeLen = avcodec_decode_audio4( pAudioCodecCtx, pFrame, &gotFrame, &packet );
        if(decodeLen < 0)
        {
            LogError("avcodec_decode_audio4 failed");
            break;
        }
        if ( gotFrame )
        {
            if(!AVClockIsEnable())
            {
                int64_t pts = sAudioParam.audioBaseTime * pFrame->pts * 1000;
                // 设置同步时钟,因为PortAudio Latency设置为了kPortAudioLatency秒,所以这里需要进去这个时间,避免视频提前kPortAudioLatency秒
                AVClockResetTime(pts - (int64_t)(kPortAudioLatency*1000));    // 单位为毫秒
                LogInfo("pts = %lld, clk = %lld", pts, AVClockGetCurTime());
                LogInfo("audio base = %lf", sAudioParam.audioBaseTime);
                AVClockEnable();
            }

            // 针对特殊情况推算channel_layout或者channels的参数值
            if ( pFrame->channels > 0 && pFrame->channel_layout == 0 )
            {
                pFrame->channel_layout = av_get_default_channel_layout( pFrame->channels );
            }
            else if ( pFrame->channels == 0 && pFrame->channel_layout > 0 )
            {
                pFrame->channels = av_get_channel_layout_nb_channels( pFrame->channel_layout );
            }

            if ( sAudioParam.swrCtx != NULL )
            {
                swr_free( &sAudioParam.swrCtx );
                sAudioParam.swrCtx = NULL;
            }

            // 配置转换器
            sAudioParam.swrCtx = swr_alloc_set_opts( NULL,
                                                     sAudioParam.wantFrame.channel_layout,
                                                     (enum AVSampleFormat) (sAudioParam.wantFrame.format),
                                                     sAudioParam.wantFrame.sample_rate,
                                                     pFrame->channel_layout,
                                                     (enum AVSampleFormat) (pFrame->format),
                                                     pFrame->sample_rate,
                                                     0, NULL );
            if ( sAudioParam.swrCtx == NULL || swr_init( sAudioParam.swrCtx ) < 0 )
            {
                LogError( "swr_init error" );
                break;
            }
            // 转换为PortAudio输出需要的数据格式
            convertLength = swr_convert( sAudioParam.swrCtx,
                                         &audioBuf + audioBufIndex,
                                         AVCODE_MAX_AUDIO_FRAME_SIZE,
                                         (const uint8_t * *) pFrame->data,
                                         pFrame->nb_samples );


            // 转换后的有效数据存储到哪里
            audioBufIndex += convertLength;
            /* 返回所有转换后的有效数据的长度 */
            convertAll += convertLength;    // 该packet解出来并做转换后的数据总长度
            av_frame_unref(pFrame);
            if ( sAudioParam.swrCtx != NULL )
            {
                swr_free( &sAudioParam.swrCtx );       // 不释放导致的内存泄漏
                sAudioParam.swrCtx = NULL;
            }
        }
        if(decodeLen > 0)
        {
            packet.size  -= decodeLen ;  // 计算当前解码剩余的数据
            if(packet.size > 0)
                LogInfo("packet.size = %d,  orig size = %d", packet.size, packet.size + decodeLen);
            packet.data += decodeLen;   // 改变数据起始位置
        }
    }
exit_:
    av_packet_unref(&packet);
    av_frame_free(&pFrame);

    return (sAudioParam.wantFrame.channels * convertAll * av_get_bytes_per_sample( (enum AVSampleFormat) (sAudioParam.wantFrame.format) ) );
}


int AudioInit(AVCodecContext	*pAudioCodecCtx)
{
    FunEntry();
    int ret;

    //1 初始化音频输出设备
    //memset(&sAudioParam, 0, sizeof(sAudioParam));
    // 初始化音频输出设备,调用PortAudio库
    Pa_Initialize();
    // 分配PaStreamParameters
    sAudioParam.outputParameters = (PaStreamParameters *)malloc(sizeof(PaStreamParameters));
    sAudioParam.outputParameters->suggestedLatency = kPortAudioLatency;       // 设置latency为0.3秒
    sAudioParam.outputParameters->sampleFormat = paFloat32;     // PCM格式
    sAudioParam.outputParameters->hostApiSpecificStreamInfo = NULL;
    // 设置音频信息, 用来打开音频设备
    sAudioParam.outputParameters->channelCount = pAudioCodecCtx->channels;      // 通道数量
    if(sAudioParam.outputParameters->sampleFormat == paFloat32)                 // 目前PortAudio我们只处理paFloat32和paInt16两种格式
    {
        sAudioParam.format = AV_SAMPLE_FMT_FLT;
    }
    else if(sAudioParam.outputParameters->sampleFormat == paInt16)
    {
        sAudioParam.format = AV_SAMPLE_FMT_S16;
    }
    else
    {
        sAudioParam.format = AV_SAMPLE_FMT_S16;
    }
    sAudioParam.sampleRate = pAudioCodecCtx->sample_rate;

    // 获取音频输出设备
    sAudioParam.outputParameters->device = Pa_GetDefaultOutputDevice();
    if(sAudioParam.outputParameters->device < 0)
    {
        LogError("Pa_GetDefaultOutputDevice failed, index = %d", sAudioParam.outputParameters->device);
    }
    // 打开一个输出流
    if((ret = Pa_OpenStream( &(sAudioParam.stream), NULL, sAudioParam.outputParameters, pAudioCodecCtx->sample_rate, 0, 0, NULL, NULL )) != paNoError)
    {
        LogError("Pa_OpenStream open failed, ret = %d", ret);
    }

    // 设置PortAudio需要的格式
    sAudioParam.wantFrame.format		= sAudioParam.format;
    sAudioParam.wantFrame.sample_rate	=  sAudioParam.sampleRate;
    sAudioParam.wantFrame.channel_layout	= av_get_default_channel_layout(sAudioParam.outputParameters->channelCount);
    sAudioParam.wantFrame.channels		= sAudioParam.outputParameters->channelCount;

    // 初始化音频队列
    PacketQueueInit( &sAudioParam.audioQueue );
    sAudioParam.packetEof = 0;

    // 初始化音频PCM格式转换器
    sAudioParam.swrCtx = NULL;

    FunExit();
    return 0;
}

void AudioPacketPush(AVPacket *packet)
{
    if(PacketQueuePut( &sAudioParam.audioQueue, packet ) != 0)
    {
        LogError("PacketQueuePut failed");
    }
}

int AudioPacketSize()
{
    return PacketQueueGetSize(&sAudioParam.audioQueue);
}

void AudioPlay( const uint8_t *data, const uint32_t size,  int stop )
{
    int chunkSize;
    int chunk = ( int )( sAudioParam.sampleRate * 0.02 ) * sAudioParam.outputParameters->channelCount * sizeof( short );
    int offset = 0;
    if ( !Pa_IsStreamActive( sAudioParam.stream ) )
        Pa_StartStream( sAudioParam.stream );
    while ( !stop && size > offset )
    {
        if( chunk < size - offset )
        {
            chunkSize = chunk;
        }
        else
        {
            chunkSize = size - offset;
        }

        if ( Pa_WriteStream( sAudioParam.stream, data + offset, chunkSize / sAudioParam.outputParameters->channelCount / sizeof( float ) ) == paUnanticipatedHostError )
            break;
        offset += chunkSize;
    }
}

void AudioClose(void)
{
    FunEntry();
    if(sAudioParam.outputParameters)
    {
        if ( sAudioParam.stream )
        {
            Pa_StopStream( sAudioParam.stream );
            Pa_CloseStream( sAudioParam.stream );
            sAudioParam.stream = NULL;
        }
        Pa_Terminate();
        free(sAudioParam.outputParameters);
    }
    if(sAudioParam.swrCtx)
    {
        swr_free( &sAudioParam.swrCtx );
    }
    FunExit();
}

void AudioSetTimeBase(double timeBase)
{
    sAudioParam.audioBaseTime = timeBase;
}

video.c

#include <iostream>       // std::cout
#include <mutex>          // std::mutex
#include <stdarg.h>
#include <string>
#include <chrono>
#include <cinttypes>
#include <ctime>
#include <sstream>
#include <iomanip>
#include <thread>

#include "video.h"
#include "av_clock.h"
#include "avpackets_queue.h"
#include "log.h"

typedef struct video_param
{
    /* 视频输出 */
    SDL_Texture		*pFrameTexture	= NULL;
    SDL_Window		*pWindow = NULL;
    SDL_Renderer 	*pRenderer = NULL;
    PacketQueue videoQueue;
    SDL_Rect	rect;
    struct SwsContext	*pSwsCtx = NULL;
    AVFrame* pFrameYUV = NULL;
    enum AVPixelFormat sPixFmt = AV_PIX_FMT_YUV420P;
    int quit = 0;
    int getFirstFrame = 0;
    double videoBaseTime  = 0.0;
    int packetEof = 0;
}T_VideoParam;

static T_VideoParam sVideoParam;

static int64_t getNowTime()
{
    auto time_now = std::chrono::system_clock::now();
    auto duration_in_ms = std::chrono::duration_cast<std::chrono::milliseconds>(time_now.time_since_epoch());
    return duration_in_ms.count();
}

int VideoInit(int width, int height, enum AVPixelFormat pix_fmt)
{
    memset(&sVideoParam, 0 ,sizeof(T_VideoParam));
    PacketQueueInit(&sVideoParam.videoQueue);

    return 0;
}

int _VideoSDL2Init(int width, int height, enum AVPixelFormat pix_fmt)
{
    sVideoParam.pWindow = SDL_CreateWindow( "Video Window",
                                            SDL_WINDOWPOS_UNDEFINED,
                                            SDL_WINDOWPOS_UNDEFINED,
                                            width, height,
                                            SDL_WINDOW_SHOWN | SDL_WINDOW_OPENGL );
    if ( !sVideoParam.pWindow )
    {
        LogError( "SDL: could not set video mode - exiting\n" );
        return -1;
    }

    SDL_RendererInfo	info;
    sVideoParam.pRenderer = SDL_CreateRenderer( sVideoParam.pWindow, -1, SDL_RENDERER_ACCELERATED | SDL_RENDERER_PRESENTVSYNC );
    if ( !sVideoParam.pRenderer )
    {
        av_log( NULL, AV_LOG_WARNING, "Failed to initialize a hardware accelerated renderer: %s\n", SDL_GetError() );
        sVideoParam.pRenderer = SDL_CreateRenderer( sVideoParam.pWindow, -1, 0 );
    }
    if ( sVideoParam.pRenderer )
    {
        if ( !SDL_GetRendererInfo( sVideoParam.pRenderer, &info ) )
            av_log( NULL, AV_LOG_VERBOSE, "Initialized %s renderer.\n", info.name );
    }

    sVideoParam.pFrameTexture = SDL_CreateTexture( sVideoParam.pRenderer, SDL_PIXELFORMAT_IYUV, SDL_TEXTUREACCESS_STREAMING, width, height);
    if(!sVideoParam.pFrameTexture)
    {
        LogError( "SDL: SDL_CreateTexture failed\n" );
        return -1;
    }
    LogInfo("pix_fmt = %d, %d\n", pix_fmt, AV_PIX_FMT_YUV420P);

    // ffmepg decode output format为YUV420
    sVideoParam.pSwsCtx = sws_getContext
            (
                width, height, pix_fmt,
                width, height, AV_PIX_FMT_YUV420P,
                SWS_BICUBIC,
                NULL,
                NULL,
                NULL
                );
    if(!sVideoParam.pSwsCtx)
    {
        LogError( "SDL: sws_getContext failed\n" );
        return -1;
    }
    sVideoParam.rect.x	= 0;
    sVideoParam.rect.y	= 0;
    sVideoParam.rect.w	= width;
    sVideoParam.rect.h	= height;

    // 分配用于保存转换后的视频帧
    sVideoParam.pFrameYUV = av_frame_alloc();
    if (!sVideoParam.pFrameYUV)
    {
        LogError( "av_frame_alloc sVideoParam.pFrameYUV  failed!\n" );
        return -1;
    }

    int numBytes = avpicture_get_size( AV_PIX_FMT_YUV420P, width,  height );
    uint8_t* buffer = (uint8_t *) av_malloc( numBytes * sizeof(uint8_t) );

    avpicture_fill( (AVPicture *) sVideoParam.pFrameYUV, buffer, AV_PIX_FMT_YUV420P, width, height );

    return 0;
}
int VideoDecodeThread( void *userdata )
{
    FunEntry();
    int64_t pts;

    AVCodecContext	*pVideoCodecCtx = (AVCodecContext *) userdata;

    AVPacket	packet;
    int			frameFinished; // 是否获取帧

    // SDL2的初始化和显示必须在同一线程进行
    _VideoSDL2Init(pVideoCodecCtx->width, pVideoCodecCtx->height, pVideoCodecCtx->pix_fmt);
    // 控制事件
    SDL_Event event;
    int64_t th = 0;
    // 分配视频帧
    AVFrame		*pVideoFrame	= NULL;
    pVideoFrame = av_frame_alloc();
    if(!pVideoFrame)
    {
        LogError( "av_frame_alloc pVideoFrame  failed!\n" );
        sVideoParam.quit = 1;
    }
    static int64_t sPrePts = 0;
    sVideoParam.getFirstFrame = 0;
    sVideoParam.packetEof = 0;
    int first = 0;
    int64_t curTime3 = 0;
    while (sVideoParam.quit != 1)
    {
        if(sVideoParam.packetEof && sVideoParam.videoQueue.size == 0)
            sVideoParam.quit = 1;

        int ret = PacketQueueTake( &sVideoParam.videoQueue, &packet, 0);

        if(ret == 0)
        {
            sVideoParam.packetEof = packet.size ? 0 : 1;
            // 解视频帧
            ( pVideoCodecCtx, pVideoFrame, &frameFinished, &packet );
            // 确定已经获取到视频帧
            if ( frameFinished )
            {
                pts =   sVideoParam.videoBaseTime * pVideoFrame->pts * 1000;        // 转为毫秒计算
                // LogInfo("video1 pts = %lld", pVideoFrame->pts);
                th = pVideoFrame->pkt_duration * sVideoParam.videoBaseTime * 1000;
                if(pts < 0)
                {
                    pts = th + sPrePts;
                }
                // LogInfo("video2 pts = %lld, th = %lld", pVideoFrame->pts, th);
                while(!sVideoParam.quit)
                {
                    int64_t diff = (pts - AVClockGetCurTime());
                    if(0==first)
                    {
                        LogInfo("\n    video pts = %lld, cur = %lld, diff = %lld", pts, AVClockGetCurTime(), diff);
                        LogInfo("video base = %lf", sVideoParam.videoBaseTime);
                    }
                    if(th = 0)
                    {
                        if(sPrePts != 0)
                        {
                            pts - sPrePts;
                        }
                        else
                            th = 30;
                    }
                    if(diff > th  && diff < 10000)      // 阈值为30毫秒, 超过一定的阈值则直接播放
                    {
                        std::this_thread::sleep_for(std::chrono::milliseconds(2));
                    }
                    else
                    {
                        break;
                    }
                    if(!AVClockIsEnable())
                    {
                        if(sVideoParam.getFirstFrame)
                            std::this_thread::sleep_for(std::chrono::milliseconds(2));
                        else
                        {
                            break;
                        }
                    }
                }
                sVideoParam.getFirstFrame  = 1;
                int64_t curTime = getNowTime();
                static int64_t sPreTime = curTime;

                if(0  == first)
                    LogInfo("cur = %lld, dif pts = %lld, time = %lld ", curTime, pts - sPrePts, curTime - sPreTime);
                sPreTime = curTime;
                sPrePts = pts;

                curTime3 = getNowTime();
                VideoDisplay(pVideoFrame, pVideoCodecCtx->height);
                //LogInfo("VideoDisplay diff = %lld", getNowTime() - curTime3);
                first = 1;
                if(pVideoCodecCtx->refcounted_frames)
                {
                    av_frame_unref( pVideoFrame);
                }
            }
            // 释放packet占用的内存
            av_packet_unref( &packet );
        }
        else
        {
            //LogInfo("sleep_for wait data , time = %lld", getNowTime());
            std::this_thread::sleep_for(std::chrono::milliseconds(10));
        }

        //LogInfo("SDL_PollEvent, time = %lld", getNowTime() - curTime3);
        SDL_PollEvent( &event );
        switch ( event.type )
        {
        case SDL_QUIT:
            SDL_Quit();
            exit( 0 );
            break;
        default:
            break;
        }
    }

    /* Free the YUV frame */
    av_free( pVideoFrame );


    FunExit();
}

void VideoDisplay(AVFrame *pVideoFrame, int height)
{
    static int64_t sPreTime = 0;
    static int64_t sPrePts = 0;
    int64_t curTime = getNowTime();
    AVFrame *pDisplayFrame;

    if(!pVideoFrame)
    {
        LogError( "pVideoFrame is null\n");
        return;
    }
    sPreTime = curTime;
    sPrePts = pVideoFrame->pts;
    if(sVideoParam.pFrameYUV)
    {
        sws_scale			// 经过scale,pts信息被清除?
                (
                    sVideoParam.pSwsCtx,
                    (uint8_t const * const *) pVideoFrame->data,
                    pVideoFrame->linesize,
                    0,
                    height,
                    sVideoParam.pFrameYUV->data,
                    sVideoParam.pFrameYUV->linesize
                    );
        pDisplayFrame = sVideoParam.pFrameYUV;
    }
    else
    {
        pDisplayFrame = pVideoFrame;
    }


    /*
     * 视频帧直接显示
     * //iPitch 计算yuv一行数据占的字节数
     */
    SDL_UpdateTexture( sVideoParam.pFrameTexture, &sVideoParam.rect, pDisplayFrame->data[0], pDisplayFrame->linesize[0] );
    SDL_RenderClear( sVideoParam.pRenderer );
    SDL_RenderCopy( sVideoParam.pRenderer, sVideoParam.pFrameTexture, &sVideoParam.rect, &sVideoParam.rect );
    SDL_RenderPresent( sVideoParam.pRenderer );
}

int VideoPacketPush(AVPacket *packet)
{
    PacketQueuePut( &sVideoParam.videoQueue, packet);
}

int VideoPacketSize()
{
    return PacketQueueGetSize(&sVideoParam.videoQueue);
}

void VideoClose()
{
    FunEntry();
    if(sVideoParam.pSwsCtx)
        sws_freeContext(sVideoParam.pSwsCtx);
    if(sVideoParam.pFrameTexture)
        SDL_DestroyTexture( sVideoParam.pFrameTexture );
    if(sVideoParam.pRenderer)
        SDL_DestroyRenderer(sVideoParam.pRenderer);
    if(sVideoParam.pWindow)
        SDL_DestroyWindow(sVideoParam.pWindow);
    if(sVideoParam.pFrameYUV)
        av_free( sVideoParam.pFrameYUV );
    FunExit();
}

int VideoGetFirstFrame()
{
    return sVideoParam.getFirstFrame;
}

void VideoSetTimeBase(double timeBase)
{
    sVideoParam.videoBaseTime = timeBase;
}
  • 2
    点赞
  • 67
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 10
    评论
ffmpeg SDL Qt播放器框架图主要包括三个核心组件:FFmpeg、SDL和Qt。 在框架图中,FFmpeg作为主要的多媒体处理库,负责解码、编码、转码和处理音视频数据。它支持多种音视频格式,并提供了丰富的API供开发者调用。FFmpeg可以从本地文件、网络流媒体或实时摄像头中获取音视频数据,并将其解码成原始音视频流。 SDL(Simple DirectMedia Layer)是一个跨平台的音视频输出和输入库,它提供了对音视频设备和图形处理的底层访问接口。SDL可以与各种操作系统、硬件和图形API相结合使用,并且具有卓越的性能和兼容性。在播放器框架中,SDL用于接收FFmpeg解码后的音视频数据,并将其输出到屏幕上进行显示。 Qt是一个功能强大的跨平台应用程序开发框架,提供了丰富的图形界面和多媒体功能。在播放器框架中,Qt用于创建播放器应用程序的用户界面,包括播放控制按钮、进度条、音量调节等交互元素。通过Qt,用户可以方便地操作播放器,选择不同的音视频文件进行播放,并进行暂停、停止、快进、快退等操作。 整个框架的工作流程如下:首先,通过Qt创建播放器应用程序的界面,并将其与SDL相关联。当用户通过界面选择要播放的音视频文件时,Qt利用FFmpeg从文件中读取音视频数据并进行解码。解码后的数据经过SDL输出到屏幕上进行实时显示。同时,Qt还负责管理播放器的状态,包括播放、暂停、停止和音量等。用户通过界面上的交互元素进行操作后,Qt会响应相应的事件,修改播放器状态,并与FFmpeg和SDL进行交互,实现音视频数据的读取、解码和输出。 通过整合FFmpeg、SDL和Qt,可以构建一个功能完善、易于使用的音视频播放器框架,实现强大的多媒体处理和播放功能。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 10
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

流媒体程序员

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值