文档章节

实时流媒体视频监控系统——的流媒体客户端——视频解码处理

go_senior_architect
 go_senior_architect
发布于 2016/08/19 16:47
字数 3902
阅读 208
收藏 2

 

Ø  项目名称:实时流媒体视频监控系统           (2016/01-2016/06)

工具环境:ffmpeg OpenCV SDL VIM VS2013 MySQL5.6 Linux Windows

项目描述:基于C/S架构的流媒体视频播放和监控系统。客户端位于Windows上,服务器架设在Linux上,采用MySQL作为用户信息的采集和存储。服务器端的消息处理利用了libevent的事件机制和进程池、线程池提高了并发量。音视频的编解码利用了ffmpeg开源库,播放则使用OpenCV和SDL来做处理。

项目职责:我主要做的是后台的服务器架构和视频的编解码。服务器的架构我们使用了一个“串”字型架构,分别采用进程池、线程池、libeven来对视频业务做调度管理和提高并发量。视频的编解码主要用了ffmpeg音视频编解码库,发送则使用了流媒体传输协议RTP和RTSP。

// MFCApplication12Dlg.cpp : 实现文件
//

#include "stdafx.h"
#include "MFCApplication12.h"
#include "MFCApplication12Dlg.h"
#include "afxdialogex.h"
#include <cv.h>
#include <cxcore.h>
#include <highgui.h>
#ifdef _DEBUG
#define new DEBUG_NEW
#endif


// 用于应用程序“关于”菜单项的 CAboutDlg 对话框

class CAboutDlg : public CDialogEx
{
public:
    CAboutDlg();

// 对话框数据
    enum { IDD = IDD_ABOUTBOX };

    protected:
    virtual void DoDataExchange(CDataExchange* pDX);    // DDX/DDV 支持

// 实现
protected:
    DECLARE_MESSAGE_MAP()
};

CAboutDlg::CAboutDlg() : CDialogEx(CAboutDlg::IDD)
{
}

void CAboutDlg::DoDataExchange(CDataExchange* pDX)
{
    CDialogEx::DoDataExchange(pDX);
}

BEGIN_MESSAGE_MAP(CAboutDlg, CDialogEx)
END_MESSAGE_MAP()


// CMFCApplication12Dlg 对话框

CMFCApplication12Dlg::CMFCApplication12Dlg(CWnd* pParent /*=NULL*/)
    : CDialogEx(CMFCApplication12Dlg::IDD, pParent)
{
    m_hIcon = AfxGetApp()->LoadIcon(IDR_MAINFRAME);
}

void CMFCApplication12Dlg::DoDataExchange(CDataExchange* pDX)
{
    CDialogEx::DoDataExchange(pDX);
    DDX_Control(pDX, IDC_STATIC_VIDEO, mVideoPic);
}

BEGIN_MESSAGE_MAP(CMFCApplication12Dlg, CDialogEx)
    ON_WM_SYSCOMMAND()
    ON_WM_PAINT()
    ON_WM_QUERYDRAGICON()
    ON_BN_CLICKED(IDOK, &CMFCApplication12Dlg::OnBnClickedOk)
    ON_BN_CLICKED(IDCANCEL, &CMFCApplication12Dlg::OnBnClickedCancel)
    ON_BN_CLICKED(IDC_BUTTON1, &CMFCApplication12Dlg::OnBnClickedButton1)
END_MESSAGE_MAP()


// CMFCApplication12Dlg 消息处理程序

BOOL CMFCApplication12Dlg::OnInitDialog()
{
    CDialogEx::OnInitDialog();

    // 将“关于...”菜单项添加到系统菜单中。

    // IDM_ABOUTBOX 必须在系统命令范围内。
    ASSERT((IDM_ABOUTBOX & 0xFFF0) == IDM_ABOUTBOX);
    ASSERT(IDM_ABOUTBOX < 0xF000);

    CMenu* pSysMenu = GetSystemMenu(FALSE);
    if (pSysMenu != NULL)
    {
        BOOL bNameValid;
        CString strAboutMenu;
        bNameValid = strAboutMenu.LoadString(IDS_ABOUTBOX);
        ASSERT(bNameValid);
        if (!strAboutMenu.IsEmpty())
        {
            pSysMenu->AppendMenu(MF_SEPARATOR);
            pSysMenu->AppendMenu(MF_STRING, IDM_ABOUTBOX, strAboutMenu);
        }
    }

    // 设置此对话框的图标。  当应用程序主窗口不是对话框时,框架将自动
    //  执行此操作
    SetIcon(m_hIcon, TRUE);            // 设置大图标
    SetIcon(m_hIcon, FALSE);        // 设置小图标

    // TODO:  在此添加额外的初始化代码
    pDC = mVideoPic.GetDC();
    hDC = pDC->GetSafeHdc();
    mVideoPic.GetClientRect(&mvideoRect);

    return TRUE;  // 除非将焦点设置到控件,否则返回 TRUE
}

void CMFCApplication12Dlg::OnSysCommand(UINT nID, LPARAM lParam)
{
    if ((nID & 0xFFF0) == IDM_ABOUTBOX)
    {
        CAboutDlg dlgAbout;
        dlgAbout.DoModal();
    }
    else
    {
        CDialogEx::OnSysCommand(nID, lParam);
    }
}

// 如果向对话框添加最小化按钮,则需要下面的代码
//  来绘制该图标。  对于使用文档/视图模型的 MFC 应用程序,
//  这将由框架自动完成。

void CMFCApplication12Dlg::OnPaint()
{
    if (IsIconic())
    {
        CPaintDC dc(this); // 用于绘制的设备上下文

        SendMessage(WM_ICONERASEBKGND, reinterpret_cast<WPARAM>(dc.GetSafeHdc()), 0);

        // 使图标在工作区矩形中居中
        int cxIcon = GetSystemMetrics(SM_CXICON);
        int cyIcon = GetSystemMetrics(SM_CYICON);
        CRect rect;
        GetClientRect(&rect);
        int x = (rect.Width() - cxIcon + 1) / 2;
        int y = (rect.Height() - cyIcon + 1) / 2;

        // 绘制图标
        dc.DrawIcon(x, y, m_hIcon);
    }
    else
    {
        CDialogEx::OnPaint();
    }
}

//当用户拖动最小化窗口时系统调用此函数取得光标
//显示。
HCURSOR CMFCApplication12Dlg::OnQueryDragIcon()
{
    return static_cast<HCURSOR>(m_hIcon);
}

#include "CvvImage.h"

extern "C"
{
    #include <libavcodec/avcodec.h>
    #include <libavformat/avformat.h>
    #include <libswscale/swscale.h>
    #include <libswresample/swresample.h>
    #include <libavutil/avstring.h>
    #include <libavutil/pixfmt.h>
    #include <libavutil/log.h>
    #include <SDL.h>
    #include <SDL_thread.h>
    #include <stdio.h>
    #include <math.h>
}
CvCapture* pCapture = NULL;

cv::Mat CopyData(AVFrame *pFrame, int width, int height)
{
    int    nChannels;
    int    stepWidth;
    uchar* pData;
    cv::Mat frameImage(cv::Size(width, height), CV_8UC3, cv::Scalar(0));
    stepWidth = frameImage.step;
    nChannels = frameImage.channels();
    pData = frameImage.data;

    for (int i = 0; i < height; i++){
        for (int j = 0; j < width; j++){
            pData[i * stepWidth + j * nChannels + 0] = pFrame->data[0][i * pFrame->linesize[0] + j * nChannels + 2];
            pData[i * stepWidth + j * nChannels + 1] = pFrame->data[0][i * pFrame->linesize[0] + j * nChannels + 1];
            pData[i * stepWidth + j * nChannels + 2] = pFrame->data[0][i * pFrame->linesize[0] + j * nChannels + 0];
        }
    }
    return frameImage;
}

void SaveFrame(AVFrame *pFrame, int width, int height, int iFrame)
{
    FILE *pFile;
    char szFileName[32];
    int y;

    sprintf(szFileName, "frame%d.ppm", iFrame);
    pFile = fopen(szFileName, "wb");
    fprintf(pFile, "P6\n%d %d\n255\n", width, height);
    for (y = 0; y < height; y++)
        fwrite(pFrame->data[0] + y * pFrame->linesize[0], 1, width*3, pFile);
    fclose(pFile);
}
//////////////////////////////////////////音视频处理//////////////////////////////////////////////
#define SDL_AUDIO_BUFFER_SIZE 1024 
#define MAX_AUDIOQ_SIZE (1 * 1024 * 1024)
#define FF_ALLOC_EVENT   (SDL_USEREVENT)
#define FF_REFRESH_EVENT (SDL_USEREVENT + 1)
#define FF_QUIT_EVENT (SDL_USEREVENT + 2)
#define AVCODEC_MAX_AUDIO_FRAME_SIZE 192000 // 1 second of 48khz 32bit audio


typedef struct PacketQueue
{
    AVPacketList *first_pkt;
    AVPacketList *last_pkt;
    int nb_packets;
    int size;
    SDL_mutex *mutex;/* The SDL mutex structure, defined in SDL_sysmutex.c */
    SDL_cond *cond;/* The SDL condition variable structure, defined in SDL_syscond.c */
} PacketQueue;//视频包的队列

typedef struct VideoState
{
    char filename[1024];
    AVFormatContext *ic;//封装格式上下文结构体,也是统领全局的结构体,保存了视频文件封装格式相关信息。
    AVCodecContext *pVideoCodecCtx;//编码器上下文结构体,保存了视频(音频)编解码相关信息。
    AVCodec *pVideoCodec;//每种视频(音频)编解码器(例如H.264解码器)对应一个该结构体
    int videoStream;//视频流
    int audioStream;//音频流

    PacketQueue videoq;//视频队列
    double video_clock;

    AVStream *audio_st;//视频文件中每个视频(音频)流对应一个该结构体
    AVFrame *audio_frame;//存储一帧解码后像素(采样)数据
    PacketQueue audioq;//音频队列
    unsigned int audio_buf_size;//视频的大小
    unsigned int audio_buf_index;//视频的下标
    AVPacket audio_pkt;//存储一帧压缩编码数据。
    uint8_t *audio_pkt_data;//typedef unsigned char uint8_t;
    int audio_pkt_size;
    uint8_t *audio_buf;
    uint8_t *audio_buf1;
    DECLARE_ALIGNED(16, uint8_t, audio_buf2)[AVCODEC_MAX_AUDIO_FRAME_SIZE * 4];
    enum AVSampleFormat audio_src_fmt;
    enum AVSampleFormat audio_tgt_fmt;
    int audio_src_channels;
    int audio_tgt_channels;
    int64_t audio_src_channel_layout;
    int64_t audio_tgt_channel_layout;
    int audio_src_freq;
    int audio_tgt_freq;
    struct SwrContext *swr_ctx;
    SDL_Thread *parse_tid;
    int quit;
    int audio_volume;

    int subtitle_stream;
    AVStream *subtitle_st;
    PacketQueue subtitleq;
    CMFCApplication12Dlg *pMainApp;
} VideoState;

VideoState *global_video_state;

void packet_queue_init(PacketQueue *q)
{
    memset(q, 0, sizeof(PacketQueue));
    q->mutex = SDL_CreateMutex();
    q->cond = SDL_CreateCond();
}

int packet_queue_put(PacketQueue *q, AVPacket *pkt)
{
    AVPacketList *pkt1;

    pkt1 = (AVPacketList *)av_malloc(sizeof(AVPacketList));
    if (!pkt1) 
    {
        return -1;
    }
    pkt1->pkt = *pkt;
    pkt1->next = NULL;

    SDL_LockMutex(q->mutex);

    if (!q->last_pkt) 
    {
        q->first_pkt = pkt1;
    }
    else
    {
        q->last_pkt->next = pkt1;
    }

    q->last_pkt = pkt1;
    q->nb_packets++;
    q->size += pkt1->pkt.size;
    SDL_CondSignal(q->cond);
    SDL_UnlockMutex(q->mutex);
    return 0;
}

static int packet_queue_get(PacketQueue *q, AVPacket *pkt, int block)
{
    AVPacketList *pkt1;
    int ret;

    SDL_LockMutex(q->mutex);

    for (;;) 
    {
        if (global_video_state->quit)
        {
            ret = -1;
            break;
        }

        pkt1 = q->first_pkt;
        if (pkt1) {
            q->first_pkt = pkt1->next;
            if (!q->first_pkt) 
            {
                q->last_pkt = NULL;
            }
            q->nb_packets--;
            q->size -= pkt1->pkt.size;
            *pkt = pkt1->pkt;

            av_free(pkt1);
            ret = 1;
            break;
        }
        else if (!block) 
        {
            ret = 0;
            break;
        }
        else {
            SDL_CondWait(q->cond, q->mutex);
        }
    }
    SDL_UnlockMutex(q->mutex);
    return ret;
}

int audio_decode_frame(VideoState *is) 
{
    int len1, len2, decoded_data_size;
    AVPacket *pkt = &is->audio_pkt;
    int got_frame = 0;
    int64_t dec_channel_layout;
    int wanted_nb_samples, resampled_data_size;

    for (;;)
    {
        while (is->audio_pkt_size > 0)
        {
            if (!is->audio_frame)
            {
                if (!(is->audio_frame = av_frame_alloc()))
                {
                    return AVERROR(ENOMEM);
                }
            }
            else
            {
                av_frame_unref(is->audio_frame);
                //avcodec_get_frame_defaults(is->audio_frame);
            }

            /*当AVPacket中装得是音频时,有可能一个AVPacket中有多个AVFrame,
            而某些解码器只会解出第一个AVFrame,这种情况我们必须循环解码出后续AVFrame*/
            len1 = avcodec_decode_audio4(is->audio_st->codec, is->audio_frame, &got_frame, pkt);
            if (len1 < 0) {
                // error, skip the frame
                is->audio_pkt_size = 0;
                break;
            }

            is->audio_pkt_data += len1;
            is->audio_pkt_size -= len1;

            if (!got_frame)
                continue;
            //执行到这里我们得到了一个AVFrame
            decoded_data_size = av_samples_get_buffer_size(NULL,
                is->audio_frame->channels, is->audio_frame->nb_samples,
                (AVSampleFormat)(is->audio_frame->format), 1);

            //得到这个AvFrame的声音布局,比如立体声
            dec_channel_layout =
                (is->audio_frame->channel_layout
                && is->audio_frame->channels
                == av_get_channel_layout_nb_channels(
                is->audio_frame->channel_layout)) ?
                is->audio_frame->channel_layout :
                av_get_default_channel_layout(
                is->audio_frame->channels);

            //这个AVFrame每个声道的采样数
            wanted_nb_samples = is->audio_frame->nb_samples;

            /*接下来判断我们之前设置SDL时设置的声音格式(AV_SAMPLE_FMT_S16),声道布局,
            * 采样频率,每个AVFrame的每个声道采样数与
            * 得到的该AVFrame分别是否相同,如有任意不同,我们就需要swr_convert该AvFrame,
            * 然后才能符合之前设置好的SDL的需要,才能播放
            */
            if (is->audio_frame->format != is->audio_src_fmt
                || dec_channel_layout != is->audio_src_channel_layout
                || is->audio_frame->sample_rate != is->audio_src_freq
                || (wanted_nb_samples != is->audio_frame->nb_samples
                && !is->swr_ctx)) {
                if (is->swr_ctx)
                    swr_free(&is->swr_ctx);
                is->swr_ctx = swr_alloc_set_opts(NULL,
                    is->audio_tgt_channel_layout, is->audio_tgt_fmt,
                    is->audio_tgt_freq, dec_channel_layout,
                    (AVSampleFormat)(is->audio_frame->format), is->audio_frame->sample_rate,
                    0, NULL);
                if (!is->swr_ctx || swr_init(is->swr_ctx) < 0) {
                    fprintf(stderr, "swr_init() failed\n");
                    break;
                }
                is->audio_src_channel_layout = dec_channel_layout;
                is->audio_src_channels = is->audio_st->codec->channels;
                is->audio_src_freq = is->audio_st->codec->sample_rate;
                is->audio_src_fmt = is->audio_st->codec->sample_fmt;
            }


            /*如果上面if判断失败,就会初始化好swr_ctx,就会如期进行转换*/
            if (is->swr_ctx)
            {
                const uint8_t **in = (const uint8_t **)is->audio_frame->extended_data;
                uint8_t *out[] = { is->audio_buf2 };
                if (wanted_nb_samples != is->audio_frame->nb_samples)
                {
                    fprintf(stdout, "swr_set_compensation \n");
                    if (swr_set_compensation(is->swr_ctx,
                        (wanted_nb_samples - is->audio_frame->nb_samples)
                        * is->audio_tgt_freq
                        / is->audio_frame->sample_rate,
                        wanted_nb_samples * is->audio_tgt_freq
                        / is->audio_frame->sample_rate) < 0)
                    {
                        fprintf(stderr, "swr_set_compensation() failed\n");
                        break;
                    }
                }


                /*转换该AVFrame到设置好的SDL需要的样子,有些旧的代码示例最主要就是少了这一部分,
                * 往往一些音频能播,一些不能播,这就是原因,比如有些源文件音频恰巧是AV_SAMPLE_FMT_S16的。
                * swr_convert 返回的是转换后每个声道(channel)的采样数
                */
                len2 = swr_convert(is->swr_ctx, out,
                    sizeof(is->audio_buf2) / is->audio_tgt_channels
                    / av_get_bytes_per_sample(is->audio_tgt_fmt),
                    in, is->audio_frame->nb_samples);
                if (len2 < 0) {
                    fprintf(stderr, "swr_convert() failed\n");
                    break;
                }
                if (len2
                    == sizeof(is->audio_buf2) / is->audio_tgt_channels
                    / av_get_bytes_per_sample(is->audio_tgt_fmt)) {
                    fprintf(stderr,
                        "warning: audio buffer is probably too small\n");
                    swr_init(is->swr_ctx);
                }
                is->audio_buf = is->audio_buf2;

                //每声道采样数 x 声道数 x 每个采样字节数
                resampled_data_size = len2 * is->audio_tgt_channels
                    * av_get_bytes_per_sample(is->audio_tgt_fmt);
            }
            else
            {
                resampled_data_size = decoded_data_size;
                is->audio_buf = is->audio_frame->data[0];
            }
            // We have data, return it and come back for more later
            return resampled_data_size;
        }

        if (pkt->data)
            av_free_packet(pkt);
        memset(pkt, 0, sizeof(*pkt));
        if (is->quit)
            return -1;
        if (packet_queue_get(&is->audioq, pkt, 1) < 0)
            return -1;

        is->audio_pkt_data = pkt->data;
        is->audio_pkt_size = pkt->size;
    }
}

void audio_callback(void *userdata, Uint8 *stream, int len)
{
    VideoState *is = (VideoState *)userdata;
    int len1, audio_data_size;

    while (len > 0)
    {
        if (is->audio_buf_index >= is->audio_buf_size)
        {
            audio_data_size = audio_decode_frame(is);

            if (audio_data_size < 0)
            {
                /* silence */
                is->audio_buf_size = 1024;
                memset(is->audio_buf, 0, is->audio_buf_size);
            }
            else
            {
                is->audio_buf_size = audio_data_size;
            }
            is->audio_buf_index = 0;
        }

        len1 = is->audio_buf_size - is->audio_buf_index;
        if (len1 > len)
        {
            len1 = len;
        }

        memcpy(stream, (uint8_t *)is->audio_buf + is->audio_buf_index, len1);
        len -= len1;
        stream += len1;
        is->audio_buf_index += len1;
    }
}

/*解码视频的线程函数*/
int video_thread(void *arg)
{
    VideoState *is = (VideoState*)arg;
    AVFrame *pavFrame;
    AVFrame *pavFrameRGB;
    AVPacket pkt, *packet=&pkt;
    int frameFinished;
    double prepts;
    double duration;
    int len1 = 0;

    pavFrame = av_frame_alloc();
    if (pavFrame == NULL)
        return -1;

    pavFrameRGB = av_frame_alloc();
    if (pavFrameRGB == NULL)
        return -1;

    unsigned int numBytes = avpicture_get_size(AV_PIX_FMT_RGB24, is->pVideoCodecCtx->width, is->pVideoCodecCtx->height);
    void *buffer = av_malloc(numBytes);
    avpicture_fill((AVPicture*)pavFrameRGB, (uint8_t*)buffer, AV_PIX_FMT_RGB24, is->pVideoCodecCtx->width, is->pVideoCodecCtx->height);

    while (packet_queue_get(&is->videoq, packet, 1) >= 0)
    {
        len1 = avcodec_decode_video2(is->pVideoCodecCtx, pavFrame, &frameFinished, packet);
        if (frameFinished)
        {
            struct SwsContext *img_convert_ctx = NULL;
            img_convert_ctx = sws_getCachedContext(img_convert_ctx,
                is->pVideoCodecCtx->width,
                is->pVideoCodecCtx->height,
                is->pVideoCodecCtx->pix_fmt,
                is->pVideoCodecCtx->width,
                is->pVideoCodecCtx->height,
                AV_PIX_FMT_RGB24,
                SWS_BICUBIC,
                NULL, NULL, NULL);
            if (!img_convert_ctx)
            {
                return 0;
            }

            //格式转换
            sws_scale(img_convert_ctx, pavFrame->data, pavFrame->linesize,
                0, is->pVideoCodecCtx->height, pavFrameRGB->data, pavFrameRGB->linesize);
            cv::Mat frameImage = CopyData(pavFrameRGB, is->pVideoCodecCtx->width, is->pVideoCodecCtx->height);
            IplImage pImage = frameImage;
            CvvImage cimg;
            cimg.CopyOf(&pImage, 1);
            cimg.DrawToHDC(is->pMainApp->hDC, &is->pMainApp->mvideoRect);
        }
    }

    return 0;
}

/*设置SDL播放声音的参数如声音采样格式,声道布局,静音值*/
int stream_component_open(VideoState *is, int stream_index) 
{
    AVFormatContext *ic = is->ic;
    AVCodecContext *codecCtx;
    AVCodec *codec;

    if (stream_index < 0 || stream_index >= ic->nb_streams)
    {
        return -1;
    }

    codecCtx = ic->streams[stream_index]->codec;
    switch (codecCtx->codec_type)
    {
    case AVMEDIA_TYPE_AUDIO:
        {
            SDL_AudioSpec wanted_spec, spec;
            int64_t wanted_channel_layout = 0;
            int wanted_nb_channels;
            const int next_nb_channels[] = { 0, 0, 1, 6, 2, 6, 4, 6 };
            
            wanted_nb_channels = codecCtx->channels;
            if (!wanted_channel_layout || wanted_nb_channels
                != av_get_channel_layout_nb_channels(
                wanted_channel_layout))
            {
                wanted_channel_layout = av_get_default_channel_layout(
                    wanted_nb_channels);
                wanted_channel_layout &= ~AV_CH_LAYOUT_STEREO_DOWNMIX;
            }

            wanted_spec.channels = av_get_channel_layout_nb_channels(
                wanted_channel_layout);
            wanted_spec.freq = codecCtx->sample_rate;
            if (wanted_spec.freq <= 0 || wanted_spec.channels <= 0) {
                fprintf(stderr, "Invalid sample rate or channel count!\n");
                return -1;
            }
            wanted_spec.format = AUDIO_S16SYS;
            wanted_spec.silence = 0;
            wanted_spec.samples = SDL_AUDIO_BUFFER_SIZE;
            wanted_spec.callback = audio_callback;
            wanted_spec.userdata = is;

            while (SDL_OpenAudio(&wanted_spec, &spec) < 0)
            {
                fprintf(stderr, "SDL_OpenAudio (%d channels): %s\n",
                    wanted_spec.channels, SDL_GetError());
                wanted_spec.channels = next_nb_channels[FFMIN(7, wanted_spec.channels)];
                if (!wanted_spec.channels)
                {
                    fprintf(stderr,
                        "No more channel combinations to tyu, audio open failed\n");
                    return -1;
                }
                wanted_channel_layout = av_get_default_channel_layout(
                    wanted_spec.channels);
            }

            if (spec.format != AUDIO_S16SYS) {
                fprintf(stderr, "SDL advised audio format %d is not supported!\n",
                    spec.format);
                return -1;
            }
            if (spec.channels != wanted_spec.channels) {
                wanted_channel_layout = av_get_default_channel_layout(spec.channels);
                if (!wanted_channel_layout) {
                    fprintf(stderr, "SDL advised channel count %d is not supported!\n",
                        spec.channels);
                    return -1;
                }
            }

            printf("%d: wanted_spec.format = %d\n", __LINE__,
                wanted_spec.format);
            printf("%d: wanted_spec.samples = %d\n", __LINE__,
                wanted_spec.samples);
            printf("%d: wanted_spec.channels = %d\n", __LINE__,
                wanted_spec.channels);
            printf("%d: wanted_spec.freq = %d\n", __LINE__, wanted_spec.freq);

            fprintf(stderr, "%d: spec.format = %d\n", __LINE__, spec.format);
            fprintf(stderr, "%d: spec.samples = %d\n", __LINE__, spec.samples);
            fprintf(stderr, "%d: spec.channels = %d\n", __LINE__, spec.channels);
            fprintf(stderr, "%d: spec.freq = %d\n", __LINE__, spec.freq);

            is->audio_src_fmt = is->audio_tgt_fmt = AV_SAMPLE_FMT_S16;
            is->audio_src_freq = is->audio_tgt_freq = spec.freq;
            is->audio_src_channel_layout = is->audio_tgt_channel_layout =
                wanted_channel_layout;
            is->audio_src_channels = is->audio_tgt_channels = spec.channels;

            codec = avcodec_find_decoder(codecCtx->codec_id);
            if (!codec || (avcodec_open2(codecCtx, codec, NULL) < 0)) {
                fprintf(stderr, "Unsupported codec!\n");
                return -1;
            }
            ic->streams[stream_index]->discard = AVDISCARD_DEFAULT;

            is->audioStream = stream_index;
            is->audio_st = ic->streams[stream_index];
            is->audio_buf_size = 0;
            is->audio_buf_index = 0;
            memset(&is->audio_pkt, 0, sizeof(is->audio_pkt));
            //开始播放
            SDL_PauseAudio(0);
        }
        break;
    case AVMEDIA_TYPE_VIDEO:
        {
            codec = avcodec_find_decoder(codecCtx->codec_id);
            is->videoStream = stream_index;
            is->pVideoCodecCtx = codecCtx;
            is->pVideoCodec = codec;

            if (!codec || (avcodec_open2(codecCtx, codec, NULL) < 0)) {
                fprintf(stderr, "Unsupported codec!\n");
                return -1;
            }
            SDL_CreateThread(video_thread, NULL, is);
        }
        break;
    }
}

/*demuxing出AVPacket*/

int read_thread(void *arg)
{
    VideoState *is = (VideoState *)arg;
    AVFormatContext *ic = NULL;
    AVPacket pkt1, *packet = &pkt1;
    int ret, i, audio_index = -1, video_index = -1;

    global_video_state = is;
    ic = avformat_alloc_context();
    if (!ic)
    {
        printf("Could not allocate context!");
        exit(0);
    }

    if (avformat_open_input(&ic, is->filename, NULL, NULL) != 0)//打开输入视频文件
    {
        return -1;
    }
    is->ic = ic;

    if (avformat_find_stream_info(ic, NULL) < 0)//获取视频文件信息
    {
        return -1;
    }
    //打印文件信息
    av_dump_format(ic, 0, is->filename, 0);

    is->audioStream = -1;
    is->videoStream = -1;
    for (i = 0; i < ic->nb_streams; i++)
    {
        if (ic->streams[i]->codec->codec_type == AVMEDIA_TYPE_AUDIO    && audio_index < 0)
        {
            audio_index = i;
        }
        if (ic->streams[i]->codec->codec_type == AVMEDIA_TYPE_VIDEO && video_index < 0)
        {
            video_index = i;
        }
    }
    if (audio_index >= 0)
    {
        //设置SDL音频流信息
        stream_component_open(is, audio_index);
    }
    if (video_index >= 0)
    {
        //设置视频信息
        stream_component_open(is, video_index);
    }
    if (is->audioStream < 0)
    {
        printf("%s: could not open audio codecs\n", is->filename);
        //exit(0);//可以屏蔽了来对付没有声音视屏的播放
    }
    if (is->videoStream < 0)
    {
        printf("%s: could not open video codecs\n", is->filename);
        //exit(0);
    }

    // main decode loop  
    for (;;)
    {
        if (is->quit)
            break;
        if (is->audioq.size > MAX_AUDIOQ_SIZE)
        {
            SDL_Delay(10);//设置视频播放快慢//10
            continue;
        }
        ret = av_read_frame(is->ic, packet);
        if (ret < 0)
        {
            if (ret == AVERROR_EOF || url_feof(is->ic->pb))
            {
                break;
            }
            if (is->ic->pb && is->ic->pb->error)
            {
                break;
            }
            continue;
        }

        if (packet->stream_index == is->audioStream) 
        {
            packet_queue_put(&is->audioq, packet);
        }
        else if (packet->stream_index == is->videoStream)
        {
            packet_queue_put(&is->videoq, packet);
        }
        else
        {
            av_free_packet(packet);
        }
    }

    while (!is->quit) 
    {
        SDL_Delay(100);////100
    }
    return 0;
}
/*
ffmpeg
SDL
ffmpeg播放音视频的流程
服务器端 通过 ffmpeg发送流媒体
客户端   接收packet并解析,分别处理图像和声音

声音和图像的同步
*/

void CMFCApplication12Dlg::OnBnClickedOk()
{
    // TODO:  在此添加控件通知处理程序代码
    //创建窗口
    //cvNamedWindow("video1", 1);
    static bool bRunVideoThread = false;

    if (bRunVideoThread) return;
    bRunVideoThread = true;

    VideoState    *is; // 纪录视频及解码器等信息的大结构体
    is = (VideoState*)av_mallocz(sizeof(VideoState));//开了一个大结构体

    //"rtp://10.101.141.114:5004"//"test.sdp"
    //1.获取待播放文件名字//"rtp://192.168.0.107:5004"  //"D:\\flv.flv"//"G:\\coding\\ds.h264"
    av_strlcpy(is->filename, "w.sdp", sizeof(is->filename));//"trp://127.0.0.1"//  "D:\\flv.flv"
                             //"G:\\coding\\M2m - Pretty Boy.mp3"
    //2.注册所有音视频解码器
    av_register_all();

    //3.初始化SDL
    if (SDL_Init(SDL_INIT_AUDIO))
    {
        fprintf(stderr, "Could not initialize SDL - %s\n", SDL_GetError());
        exit(1);
    }
    is->pMainApp = this;

    //4.初始化包队列
    packet_queue_init(&is->videoq);//视频队列
    packet_queue_init(&is->audioq);//音频队列

    //pthread_create(&tid, NULL, threadproc, this);
    CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)read_thread, is, 0, NULL);
}

void CMFCApplication12Dlg::OnBnClickedCancel()
{
    // TODO:  在此添加控件通知处理程序代码
    //cvDestroyWindow("video1");
    //cvReleaseCapture(&pCapture);
    CDialogEx::OnCancel();
}

void CMFCApplication12Dlg::OnBnClickedButton1()
{
    // TODO:  在此添加控件通知处理程序代码
    cvReleaseCapture(&pCapture);
    pDC->FillSolidRect(mvideoRect, RGB(0,0,0));
}

#if 0
DWORD WINAPI ThreadShowView(LPVOID lpThreadParameter)
{
    //获取摄像头
    //pCapture = cvCaptureFromCAM(0);
    //pCapture = cvCaptureFromFile("D:/movie video/侏罗纪世界.HD.576p.中英双字幕.rmvb");

    //ffmpeg定义的变量参数
    AVFormatContext *pFormatCtx = NULL;
    AVCodecContext *pVideoCodecCtx;
    AVCodecContext *pAudioCodecCtx;
    AVCodec *pVideoCodec;
    AVCodec *pAudioCodec;
    AVFrame *pavFrame;
    AVFrame *pavFrameRGB;
    AVPacket packet;
    int frameFinished;
    int videoStream = -1;
    int audioStream = -1;
    long prepts = 0;

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

    //打开视频文件
    if (avformat_open_input(&pFormatCtx, "D:\\movie video\\侏罗纪世界.HD.576p.中英双字幕.rmvb", NULL, NULL) != 0)
        return -1;

    if (avformat_find_stream_info(pFormatCtx, NULL) < 0)
        return -1;

    videoStream = -1;
    audioStream = -1;
    for (int i = 0; i < pFormatCtx->nb_streams; ++i)
    {
        if (pFormatCtx->streams[i]->codec->codec_type == AVMEDIA_TYPE_VIDEO)
        {
            videoStream = i;
        }
        else if (pFormatCtx->streams[i]->codec->codec_type == AVMEDIA_TYPE_AUDIO)
        {
            audioStream = i;
        }
    }
    if (videoStream == -1)
        return-1;
    if (audioStream == -1)
        return-1;

    pVideoCodecCtx = pFormatCtx->streams[videoStream]->codec;
    pVideoCodec = avcodec_find_decoder(pVideoCodecCtx->codec_id);
    if (pVideoCodec == NULL)
        return-1;

    pAudioCodecCtx = pFormatCtx->streams[audioStream]->codec;
    pAudioCodec = avcodec_find_decoder(pAudioCodecCtx->codec_id);

    //打开视频解码器
    if (avcodec_open2(pVideoCodecCtx, pVideoCodec, NULL) <0)
        return-1;

    //打开音频解码器
    if (avcodec_open2(pAudioCodecCtx, pAudioCodec, NULL) < 0)
    {
        return -1;
    }

    /*SDL*/
    uint64_t out_channel_layout = AV_CH_LAYOUT_STEREO;
    int out_nb_samples = pAudioCodecCtx->frame_size;
    AVSampleFormat out_sample_fmt = AV_SAMPLE_FMT_S16;
    int out_sample_rate = 44100;
    int out_channels = av_get_channel_layout_nb_channels(out_channel_layout);
    //输出音频数据大小,一定小于输出内存。  
    int out_linesize;
    int out_buffer_size = av_samples_get_buffer_size(&out_linesize, pAudioCodecCtx->channels, pAudioCodecCtx->frame_size,
        pAudioCodecCtx->sample_fmt, 1);
    uint8_t *out_buffer = new uint8_t[out_buffer_size];


    if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO | SDL_INIT_TIMER))
        return -1;
    SDL_AudioSpec wanted_spec;
    wanted_spec.freq = out_sample_rate;
    wanted_spec.format = AUDIO_S16SYS;
    wanted_spec.channels = out_channels;
    wanted_spec.silence = 0;
    wanted_spec.samples = out_nb_samples;
    wanted_spec.callback = fill_audio;
    wanted_spec.userdata = pAudioCodecCtx;
    if (SDL_OpenAudio(&wanted_spec, NULL) < 0)
        return -1;
    int64_t in_channel_layout = av_get_default_channel_layout(pAudioCodecCtx->channels);
    //Swr  
    struct SwrContext *au_convert_ctx;
    au_convert_ctx = swr_alloc();
    au_convert_ctx = swr_alloc_set_opts(au_convert_ctx, AV_CH_LAYOUT_STEREO, AV_SAMPLE_FMT_S16, out_sample_rate,
        pAudioCodecCtx->channel_layout, pAudioCodecCtx->sample_fmt, pAudioCodecCtx->sample_rate, 0, NULL);
    swr_init(au_convert_ctx);

    //Play  
    SDL_PauseAudio(0);

    ///////////////////////////////////////////////////////////////
    pavFrame = av_frame_alloc();
    if (pavFrame == NULL)
        return -1;

    pavFrameRGB = av_frame_alloc();
    if (pavFrameRGB == NULL)
        return -1;

    unsigned int numBytes = avpicture_get_size(AV_PIX_FMT_RGB24, pVideoCodecCtx->width, pVideoCodecCtx->height);
    void *buffer = av_malloc(numBytes);
    avpicture_fill((AVPicture*)pavFrameRGB, (uint8_t*)buffer, AV_PIX_FMT_RGB24, pVideoCodecCtx->width, pVideoCodecCtx->height);

    int i = 0;
    while (av_read_frame(pFormatCtx, &packet) >= 0)
    {
        /*
        if (packet.stream_index == videoStream)
        {
        avcodec_decode_video2(pVideoCodecCtx, pavFrame, &frameFinished, &packet);
        if (frameFinished)
        {
        struct SwsContext *img_convert_ctx = NULL;
        img_convert_ctx = sws_getCachedContext(img_convert_ctx,
        pVideoCodecCtx->width,
        pVideoCodecCtx->height,
        pVideoCodecCtx->pix_fmt,
        pVideoCodecCtx->width,
        pVideoCodecCtx->height,
        AV_PIX_FMT_RGB24,
        SWS_BICUBIC,
        NULL, NULL, NULL);
        if (!img_convert_ctx)
        {
        return 0;
        }

        sws_scale(img_convert_ctx, pavFrame->data, pavFrame->linesize,
        0, pVideoCodecCtx->height, pavFrameRGB->data, pavFrameRGB->linesize);
        if (i++ < 50)
        {
        SaveFrame(pavFrameRGB, pVideoCodecCtx->width, pVideoCodecCtx->height, i);
        }

        cv::Mat frameImage = CopyData(pavFrameRGB, pVideoCodecCtx->width, pVideoCodecCtx->height, packet.pts - prepts);
        IplImage pImage = frameImage;

        CvvImage cimg;
        cimg.CopyOf(&pImage, 1);
        cimg.DrawToHDC(pMainApp->hDC, &pMainApp->mvideoRect);

        prepts = packet.pts;
        }
        av_free_packet(&packet);
        }

        else */if (packet.stream_index == audioStream)
        {
            avcodec_decode_audio4(pAudioCodecCtx, pavFrame, &frameFinished, &packet);
            if (frameFinished)
            {
                swr_convert(au_convert_ctx, &out_buffer, out_linesize, (const uint8_t **)pavFrame->data, pavFrame->nb_samples);
                //FIX:FLAC,MP3,AAC Different number of samples
                if (wanted_spec.samples != pavFrame->nb_samples){
                    SDL_CloseAudio();
                    out_nb_samples = pavFrame->nb_samples;
                    out_buffer_size = av_samples_get_buffer_size(NULL, out_channels, out_nb_samples, out_sample_fmt, 1);
                    wanted_spec.samples = out_nb_samples;
                    SDL_OpenAudio(&wanted_spec, NULL);
                }

                audio_chunk = (Uint8 *)out_buffer;
                audio_len = out_buffer_size;
                audio_pos = audio_chunk;

                //Play
                SDL_PauseAudio(0);
                while (audio_len>0)//Wait until finish
                    SDL_Delay(2);
            }
            av_free_packet(&packet);
        }
    }

    /*
    释放资源
    av_free(buffer);
    av_free(pavFrame);
    av_free(pavFrameRGB);
    avcodec_close(pCodecCtx);
    avformat_close_input(&pFormatCtx);

    //释放SDL的资源
    swr_free(&au_convert_ctx);
    SDL_CloseAudio();//Close SDL
    SDL_Quit();
    av_free(out_buffer);
    avcodec_close(pCodecCtx);
    av_close_input_file(pFormatCtx);
    */


    //显示视屏
    //while (1)
    //{
    //声明IplImage指针
    //IplImage *pFrame = cvQueryFrame(pCapture);
    //if (!pFrame)
    //break;
    //cvShowImage("video1", pFrame);
    //ResizeImage(pFrame);
    //ShowImage(TheImage);
    /*
    int w = mvideoRect.Width();
    int h = mvideoRect.Height();

    //缩放图片
    IplImage *pShowImage = NULL;
    cvSetImageROI(pShowImage, cvRect(0, 0, w, h));
    cvResize(pFrame, pShowImage);
    cvResetImageROI(pShowImage);
    */
    //CvvImage cimg;
    //cimg.CopyOf(pFrame, 1);
    //cimg.DrawToHDC(pMainApp->hDC, &pMainApp->mvideoRect);
    //}

    return 0;
}
#endif
 

© 著作权归作者所有

go_senior_architect
粉丝 0
博文 3
码字总数 5998
作品 0
西安
私信 提问
巨峰科技圆满完成市级示范性高中视频监控项目

一、项目背景 漯河市第二高级中学位于河南省漯河市,属漯河市市级示范性高中。为了确保学校及周边的治安安全,以及满足学校内的监控视频观看需求,需要提供一套完整的视频监控解决方案。 杭州...

安防资讯
2018/04/16
0
0
UCan下午茶 悟有所值——深度学习在流媒体领域的应用之道

视频门户已经存在了近 10 年了,视频技术似乎已经没有什么新鲜可以玩。但 2016 年开始,从 papi 酱这样的网红掀起了移动短视频爆发的风潮,一夜之间雨后春笋般涌现的几百家移动直播 APP,巨头...

UCloudTech
2017/10/24
57
0
UCan下午茶 悟有所值——深度学习在流媒体领域的应用之道

视频门户已经存在了近 10 年了,视频技术似乎已经没有什么新鲜可以玩。但 2016 年开始,从 papi 酱这样的网红掀起了移动短视频爆发的风潮,一夜之间雨后春笋般涌现的几百家移动直播 APP,巨头...

UCloudTech
2017/10/24
21
0
直播,音视频编码器和解码器(EasyDarwin)-Android

使用摄像头采集视频数据,并通过MediaCodec进行H264编码,之后打包成RTSP格式并上传的。 TextuewView也提供了一个setTransform方法,该方法接收一个matrix参数,使用该参数对当前的渲染内容进...

shareus
2018/05/18
0
0
Android视频直播的实现(推流完整实现001)

http://blog.csdn.net/huaxun66/article/details/53427771 http://blog.csdn.net/huaxun66/article/details/53427771 最近一段时间,视频直播可谓大火。在视频直播领域,有不同的商家提供各种...

stn_lcd
2017/02/20
0
0

没有更多内容

加载失败,请刷新页面

加载更多

CSS定位

CSS定位 relative相对定位 absolute绝对定位 fixed和sticky及zIndex relative相对定位 position特性:css position属性用于指定一个元素在文档中的定位方式。top、right、bottom、left属性则...

studywin
32分钟前
5
0
从零基础到拿到网易Java实习offer,我做对了哪些事

作为一个非科班小白,我在读研期间基本是自学Java,从一开始几乎零基础,只有一点点数据结构和Java方面的基础,到最终获得网易游戏的Java实习offer,我大概用了半年左右的时间。本文将会讲到...

Java技术江湖
昨天
5
0
程序性能checklist

程序性能checklist

Moks角木
昨天
7
0
VUE 计算属性

本文转载于:专业的前端网站▶VUE 计算属性 1、示例代码 <!DOCTYPE html><html lang="zh"> <head> <meta charset="UTF-8" /> <title>vue示例</title> </hea......

前端老手
昨天
7
0
快速搭建LNMT平台和环境部署 Tomcat详解

Tomcat部署的基本概念 1. CATALINA_HOME与CATALINA_BASE分别指什么?     CATALINA_HOME指的是Tomcat的安装目录     bin:\\Tomcat一些脚本存放目录,比如启动脚本startup.bat/start...

网络小虾米
昨天
9
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部