博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
九、 一个简单的播放器(各自同步)
阅读量:7156 次
发布时间:2019-06-29

本文共 17156 字,大约阅读时间需要 57 分钟。

[TOC]

开始前的BB

开始准备搞播放器了,还不知道怎么跟大佬们讲,头疼

想来想去,我感觉先实现一个简单的视频播放器,视频和音频自同步,来让各位大佬们先来体验一下,有个大体的脉络

老夫撸码就是一把梭

我们先粗暴的分为两个线程,一个负责音频的播放,一个负责视频的播放,根据之前的我们写过的东西,我们来改一改

chapter_09/中新建两个类VideoThreadAudioThread,一个负责视频的解码,一个负责音频的解码,渲染的话我们新建一个AVRender,专门负责渲染以及窗口事件的管理

千言万语注释中

AVRender 渲染以及事件处理

AVRender.h

//// Created by MirsFang on 2019-03-25.//#ifndef LEARNFFMPEG_AVRENDER_H#define LEARNFFMPEG_AVRENDER_H#define WINDOW_WIDTH 1080#define WINDOW_HEIGHT 720#include 
extern "C" {#include
#include
}/** 音视频渲染器 **/class AVRender {public: AVRender(); ~AVRender(); /** * 打开音频 * * @param sample_rate 采样率 * @param channel 通道数 * @param samples 采样大小(一帧的音频数据大小) * @param userdata 用户数据 * @param fillaudio 回调函数 */ void openAudio(int sample_rate, Uint8 channel, Uint16 samples, void *userdata, void (*fill_audio)(void *codecContext, Uint8 *stream, int len)); /** 循环获取事件 **/ void loopEvent(); /** 渲染视频 * * @param frame 视频帧 * @param duration 帧持续的时间 */ void renderVideo(AVFrame *frame,Uint32 duration);private: /** SDL窗口 **/ SDL_Window *window; /** SDL渲染者 **/ SDL_Renderer *render; /** SDL纹理 **/ SDL_Texture *texture; /** 显示区域 **/ SDL_Rect rect; /** 自己想要的输出的音频格式 **/ SDL_AudioSpec wantSpec;};#endif //LEARNFFMPEG_AVRENDER_H复制代码

AVRender.cpp

//// Created by MirsFang on 2019-03-25.//#include "AVRender.h"AVRender::AVRender() {    //初始化SDL2    if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO | SDL_INIT_EVENTS)) {        std::cout << "[error] SDL Init error !" << std::endl;        return;    }    //创建window    window = SDL_CreateWindow("LearnFFmpeg", SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, WINDOW_WIDTH,                              WINDOW_HEIGHT, SDL_WINDOW_OPENGL);    if (!window) {        std::cout << "[error] SDL Create window error!" << std::endl;        return;    }    //创建Render    render = SDL_CreateRenderer(window, -1, 0);    //创建Texture    texture = SDL_CreateTexture(render, SDL_PIXELFORMAT_IYUV, SDL_TEXTUREACCESS_STREAMING, WINDOW_WIDTH, WINDOW_HEIGHT);    //初始化Rect    rect.x = 0;    rect.y = 0;    rect.w = WINDOW_WIDTH;    rect.h = WINDOW_HEIGHT;}AVRender::~AVRender() {    SDL_CloseAudio();    SDL_Quit();    if(render)SDL_DestroyRenderer(render);    if(texture)SDL_DestroyTexture(texture);    if(window)SDL_DestroyWindow(window);}void AVRender::loopEvent() {    SDL_Event event;    for (;;) {        SDL_PollEvent(&event);        switch (event.type) {            case SDL_KEYDOWN:                switch (event.key.keysym.sym) {                }                break;            case SDL_QUIT:                return;            default:                break;        }    }}void AVRender::renderVideo(AVFrame *frame, Uint32 duration) {    if (frame == nullptr)return;    //上传YUV到Texture    SDL_UpdateYUVTexture(texture, &rect,                         frame->data[0], frame->linesize[0],                         frame->data[1], frame->linesize[1],                         frame->data[2], frame->linesize[2]    );    SDL_RenderClear(render);    SDL_RenderCopy(render, texture, NULL, &rect);    SDL_RenderPresent(render);    SDL_Delay(duration);}void AVRender::openAudio(int sample_rate, Uint8 channel, Uint16 samples, void *userdata,                         void (*fill_audio)(void *, Uint8 *, int)) {    //初始化SDL中自己想设置的参数    wantSpec.freq = sample_rate;    wantSpec.format = AUDIO_S16SYS;    wantSpec.channels = channel;    wantSpec.silence = 0;    wantSpec.samples = samples;    wantSpec.callback = fill_audio;    wantSpec.userdata = userdata;    //打开音频之后wantSpec的值可能会有改动,返回实际设备的参数值    if (SDL_OpenAudio(&wantSpec, NULL) < 0) {        std::cout << "[error] open audio error" << std::endl;        return;    }    SDL_PauseAudio(0);}复制代码

VideoThread 视频解码

视频解码类VideoThread.h

//// Created by MirsFang on 2019-03-25.//#ifndef LEARNFFMPEG_VIDEOTHREAD_H#define LEARNFFMPEG_VIDEOTHREAD_H#include 
#include
#include "AVRender.h"extern "C" {#include
#include
};/** 视频线程 **/class VideoThread {public: VideoThread(); ~VideoThread(); /** 设置视频路径 **/ void setUrl(const char *url); /** 设置渲染器 **/ void setRender(AVRender *render); /** 开始运行线程 **/ void start();private: AVFormatContext *format_context; AVCodecContext *codec_context; AVCodec *codec; AVPacket *packet; AVFrame *frame; const char *url; int video_index; pthread_t pid; pthread_mutex_t mutex; AVRender *avRender; double last_pts = 0; /** 帧间距同步 **/ bool is_interval_sync = true; static void *start_thread(void *arg); void run(); /** 初始化解码器 **/ void prepare_codec(); /** 解码数据帧 **/ void decodec_frame(); /** * 根据帧率获取显示时间 * @param frame_rate 帧率 * @return 需要显示的时长 */ Uint32 sync_frame_rate(double frame_rate); /** * 根据帧间隔获取一帧显示的时长 * @param timebase * @param pts 秒 * @return */ double sync_frame_interval(AVRational timebase, int pts);};#endif //LEARNFFMPEG_VIDEOTHREAD_H复制代码

VideoThread.cpp

//// Created by MirsFang on 2019-03-25.//#include "VideoThread.h"VideoThread::VideoThread() {}VideoThread::~VideoThread() {    if (format_context != nullptr) avformat_close_input(&format_context);    if (codec_context != nullptr) avcodec_free_context(&codec_context);    if (packet != nullptr) av_packet_free(&packet);    if (frame != nullptr) av_frame_free(&frame);}void VideoThread::start() {    prepare_codec();    if (pthread_create(&pid, NULL, start_thread, (void *) this) != 0) {        std::cout << "初始化视频线程失败!" << std::endl;        return;    }}void *VideoThread::start_thread(void *arg) {    VideoThread *audioThread = (VideoThread *) arg;    audioThread->run();    return nullptr;}void VideoThread::run() {    std::cout << "视频线程运行中..." << std::endl;    decodec_frame();}void VideoThread::setRender(AVRender *render) {    this->avRender = render;}void VideoThread::setUrl(const char *url) {    this->url = url;}void VideoThread::prepare_codec() {    int retcode;    //初始化FormatContext    format_context = avformat_alloc_context();    if (!format_context) {        std::cout << "[error] alloc format context error!" << std::endl;        return;    }    //打开输入流    retcode = avformat_open_input(&format_context, url, nullptr, nullptr);    if (retcode != 0) {        std::cout << "[error] open input error!" << std::endl;        return;    }    //读取媒体文件信息    retcode = avformat_find_stream_info(format_context, NULL);    if (retcode != 0) {        std::cout << "[error] find stream error!" << std::endl;        return;    }    //分配codecContext    codec_context = avcodec_alloc_context3(NULL);    if (!codec_context) {        std::cout << "[error] alloc codec context error!" << std::endl;        return;    }    //寻找到视频流的下标    video_index = av_find_best_stream(format_context, AVMEDIA_TYPE_VIDEO, -1, -1, NULL, 0);    //将视频流的的编解码信息拷贝到codecContext中    retcode = avcodec_parameters_to_context(codec_context, format_context->streams[video_index]->codecpar);    if (retcode != 0) {        std::cout << "[error] parameters to context error!" << std::endl;        return;    }    //查找解码器    codec = avcodec_find_decoder(codec_context->codec_id);    if (codec == nullptr) {        std::cout << "[error] find decoder error!" << std::endl;        return;    }    //打开解码器    retcode = avcodec_open2(codec_context, codec, nullptr);    if (retcode != 0) {        std::cout << "[error] open decodec error!" << std::endl;        return;    }    //初始化一个packet    packet = av_packet_alloc();    //初始化一个Frame    frame = av_frame_alloc();}void VideoThread::decodec_frame() {    int sendcode = 0;    //计算帧率    double frameRate = av_q2d(format_context->streams[video_index]->avg_frame_rate);    //计算显示的时间    Uint32 display_time_ms = 0;    if (!is_interval_sync) {        display_time_ms = sync_frame_rate(frameRate);    }    //记录帧间延迟    clock_t start = 0, finish = 0;    //读取包    while (av_read_frame(format_context, packet) == 0) {        if (packet->stream_index != video_index)continue;        //接受解码后的帧数据        while (avcodec_receive_frame(codec_context, frame) == 0) {            /**             * 如果开启帧间隔同步模式,那么是根据             *             *  显示时长 = 当前帧 - 上一帧 - 单帧解码耗时             *             *  可得出当前帧真正要显示的时间             *             * **/            if (is_interval_sync) {                //计算上一帧与当前帧的延时                display_time_ms = (Uint32) (                        sync_frame_interval(format_context->streams[video_index]->time_base, frame->pts) * 1000);                //帧解码结束时间                finish = clock();                double diff_time = (finish - start) / 1000;                //减去帧间解码时差 帧解码开始时间 - 帧解码结束时间                if (display_time_ms > diff_time)display_time_ms = display_time_ms - (Uint32) diff_time;            }            //绘制图像            if (avRender)avRender->renderVideo(frame, display_time_ms);            av_frame_unref(frame);            //帧解码开始时间            start = clock();        }        //发送解码前的包数据        sendcode = avcodec_send_packet(codec_context, packet);        //根据发送的返回值判断状态        if (sendcode == 0) {//            std::cout << "[debug] " << "SUCCESS" << std::endl;        } else if (sendcode == AVERROR_EOF) {            std::cout << "[debug] " << "EOF" << std::endl;        } else if (sendcode == AVERROR(EAGAIN)) {            std::cout << "[debug] " << "EAGAIN" << std::endl;        } else {            std::cout << "[debug] " << av_err2str(AVERROR(sendcode)) << std::endl;        }        av_packet_unref(packet);    }}Uint32 VideoThread::sync_frame_rate(double frame_rate) {    return 1 * 1000 / frame_rate;}double VideoThread::sync_frame_interval(AVRational timebase, int pts) {    double display = (pts - last_pts) * av_q2d(timebase);    last_pts = pts;    std::cout << "pts : " << pts * av_q2d(timebase) << "   --  display :" << display << std::endl;    return display;}复制代码

AudioThread 音频解码

AudioThread

//// Created by MirsFang on 2019-03-25.//#ifndef LEARNFFMPEG_AUDIOTHREAD_H#define LEARNFFMPEG_AUDIOTHREAD_H#include 
#include
extern "C" {#include
#include
#include
};#include "AVRender.h"/** * 音频线程 */class AudioThread {public: AudioThread(); ~AudioThread(); void setUrl(const char *url); /** 开启线程 **/ void start(); /** 设置渲染器 **/ void setRender(AVRender *render);private: /** 重采样上下文 **/ SwrContext *convert_context; AVFormatContext *format_context; AVCodecContext *codec_context; AVCodec *codec; AVPacket *packet; AVFrame *frame; int audioIndex = -1; uint64_t out_chn_layout = AV_CH_LAYOUT_STEREO; //输出的通道布局 双声道 enum AVSampleFormat out_sample_fmt = AV_SAMPLE_FMT_S16; //输出的声音格式 int out_sample_rate = 44100; //输出的采样率 int out_nb_samples = -1; //输出的音频采样 int out_channels = -1; //输出的通道数 int out_buffer_size = -1; //输出buff大小 unsigned char *outBuff = NULL;//输出的Buffer数据 uint64_t in_chn_layout = -1; //输入的通道布局 pthread_t pid; pthread_mutex_t mutex; AVRender *av_render; const char *url; static void *start_thread(void *arg); void run(); /** 初始化解码器 **/ void prepare_codec();};#endif //LEARNFFMPEG_AUDIOTHREAD_H复制代码

AudioThread.cpp

//// Created by MirsFang on 2019-03-25.//#include "AudioThread.h"#define MAX_AUDIO_FRAME_SIZE 192000 // 1 second of 48khz 32bit audio   48000 * (32/8)//一帧PCM的数据长度unsigned int audioLen = 0;unsigned char *audioChunk = nullptr;//当前读取的位置unsigned char *audioPos = nullptr;/** 被SDL2调用的回调函数 当需要获取数据喂入硬件播放的时候调用 **/void fill_audio(void *codecContext, Uint8 *stream, int len) {    //SDL2中必须首先使用SDL_memset()将stream中的数据设置为0    SDL_memset(stream, 0, len);    if (audioLen == 0)        return;    len = (len > audioLen ? audioLen : len);    //将数据合并到 stream 里    SDL_MixAudio(stream, audioPos, len, SDL_MIX_MAXVOLUME);    //一帧的数据控制    audioPos += len;    audioLen -= len;}AudioThread::AudioThread() {}AudioThread::~AudioThread() {    if (format_context != nullptr) avformat_close_input(&format_context);    if (codec_context != nullptr) avcodec_free_context(&codec_context);    if (packet != nullptr) av_packet_free(&packet);    if (frame != nullptr) av_frame_free(&frame);    if (convert_context != nullptr) swr_free(&convert_context);}void AudioThread::start() {    prepare_codec();    if (pthread_create(&pid, NULL, start_thread, (void *) this) != 0) {        std::cout << "初始化音频线程失败!" << std::endl;        return;    }}void *AudioThread::start_thread(void *arg) {    AudioThread *audioThread = (AudioThread *) arg;    audioThread->run();    return nullptr;}void AudioThread::run() {    std::cout << "音频线程已启动" << std::endl;    //循环读取packet并且解码    int sendcode = 0;    while (av_read_frame(format_context, packet) >= 0) {        if (packet->stream_index != audioIndex)continue;        //接受解码后的音频数据        while (avcodec_receive_frame(codec_context, frame) == 0) {            swr_convert(convert_context, &outBuff, MAX_AUDIO_FRAME_SIZE, (const uint8_t **) frame->data,                        frame->nb_samples);            //如果没有播放完就等待1ms            while (audioLen > 0)                SDL_Delay(1);            //同步数据            audioChunk = (unsigned char *) outBuff;            audioPos = audioChunk;            audioLen = out_buffer_size;            av_frame_unref(frame);        }        //发送解码前的包数据        sendcode = avcodec_send_packet(codec_context, packet);        //根据发送的返回值判断状态        if (sendcode == 0) {//            std::cout << "[debug] " << "SUCCESS" << std::endl;        } else if (sendcode == AVERROR_EOF) {            std::cout << "[debug] " << "EOF" << std::endl;        } else if (sendcode == AVERROR(EAGAIN)) {            std::cout << "[debug] " << "EAGAIN" << std::endl;        } else {            std::cout << "[debug] " << av_err2str(AVERROR(sendcode)) << std::endl;        }        av_packet_unref(packet);    }}void AudioThread::setRender(AVRender *render) {    this->av_render = render;}void AudioThread::prepare_codec() {    int retcode;    //初始化FormatContext    format_context = avformat_alloc_context();    if (!format_context) {        std::cout << "[error] alloc format context error!" << std::endl;        return;    }    //打开输入流    retcode = avformat_open_input(&format_context, url, nullptr, nullptr);    if (retcode != 0) {        std::cout << "[error] open input error!" << std::endl;        return;    }    //读取媒体文件信息    retcode = avformat_find_stream_info(format_context, NULL);    if (retcode != 0) {        std::cout << "[error] find stream error!" << std::endl;        return;    }    //分配codecContext    codec_context = avcodec_alloc_context3(NULL);    if (!codec_context) {        std::cout << "[error] alloc codec context error!" << std::endl;        return;    }    //寻找到音频流的下标    audioIndex = av_find_best_stream(format_context, AVMEDIA_TYPE_AUDIO, -1, -1, NULL, 0);    //将视频流的的编解码信息拷贝到codecContext中    retcode = avcodec_parameters_to_context(codec_context, format_context->streams[audioIndex]->codecpar);    if (retcode != 0) {        std::cout << "[error] parameters to context error!" << std::endl;        return;    }    //查找解码器    codec = avcodec_find_decoder(codec_context->codec_id);    if (codec == nullptr) {        std::cout << "[error] find decoder error!" << std::endl;        return;    }    //打开解码器    retcode = avcodec_open2(codec_context, codec, nullptr);    if (retcode != 0) {        std::cout << "[error] open decodec error!" << std::endl;        return;    }    //初始化一个packet    packet = av_packet_alloc();    //初始化一个Frame    frame = av_frame_alloc();    /** ########## 获取实际音频的参数 ##########**/    //单个通道中的采样数    out_nb_samples = codec_context->frame_size;    //输出的声道数    out_channels = av_get_channel_layout_nb_channels(out_chn_layout);    //输出音频的布局    in_chn_layout = av_get_default_channel_layout(codec_context->channels);    /** 计算重采样后的实际数据大小,并分配空间 **/    //计算输出的buffer的大小    out_buffer_size = av_samples_get_buffer_size(NULL, out_channels, out_nb_samples, out_sample_fmt, 1);    //分配输出buffer的空间    outBuff = (unsigned char *) av_malloc(MAX_AUDIO_FRAME_SIZE * 2); //双声道    //初始化SDL中自己想设置的参数    if (av_render)av_render->openAudio(out_sample_rate, out_channels, out_nb_samples, codec_context, fill_audio);    convert_context = swr_alloc_set_opts(NULL, out_chn_layout, out_sample_fmt, out_sample_rate,                                          in_chn_layout, codec_context->sample_fmt, codec_context->sample_rate, 0,                                          NULL);    //初始化SwResample的Context    swr_init(convert_context);}void AudioThread::setUrl(const char *url) {    this->url = url;}复制代码

我们在Main方法中

#ifdef chapter_09    //实例化渲染器    AVRender* render = new AVRender();    //初始化视频线程    VideoThread *videoThread = new VideoThread();    videoThread->setRender(render);    videoThread->setUrl(url);    //初始化音频线程    AudioThread *audioThread = new AudioThread();    audioThread->setRender(render);    audioThread->setUrl(url);    //开启音视频线程    videoThread->start();    audioThread->start();    //事件循环    render->loopEvent();#endif复制代码

如果没错,那么就应该正常的播放视频了。。。

祝各位大佬们好运

未完持续 ...

转载于:https://juejin.im/post/5cad78c9f265da03a85aa56a

你可能感兴趣的文章
电梯的测试点
查看>>
限时订单设计
查看>>
wordpress生成xml
查看>>
国外41个精彩的电子商务网站设计欣赏
查看>>
ie8下input文字偏上select文字偏下
查看>>
es6新语法Object.assign()
查看>>
Django 路由、模板和模型系统
查看>>
JAVA递归
查看>>
想要在launcher中模拟按home键。
查看>>
洛谷P3381 最小费用最大流
查看>>
Centos 从零开始 (二)
查看>>
mongodb的用户管理及安全认证
查看>>
[转]定位占用oracle数据库cpu过高的sql
查看>>
MySQL移动数据目录出现权限问题
查看>>
java数据类型+运算符+控制流
查看>>
numpy切片和布尔型索引
查看>>
MySQL执行计划各列含义
查看>>
angular1的复选框指令--checklistModel
查看>>
【leetcode】74. Search a 2D Matrix & 240. Search a 2D Matrix II
查看>>
LightOJ - 1077 How Many Points
查看>>