linux下播放器的设计和开发

本文根据DawnLightPlayer的开发经验写成。DawnLithtPlayer是今天3月份开始,和maddrone一起在业余时间开发的一个跨平台,多线程的播放器,主要是在Linux下面开发的,文中所用示例代码均截自其中。
DawnLightPlayer目前可以运行在Linux和Windows系统上,并使用VC和Python开发了GUI,支持大部分的音视频文件格式和网络流,另外新增对CMMB协议的支持,不支持 RMVB, SWF 等尚未公开协议的视频文件格式。

目录:
一. 播放器的流程
   1. 输入
   2. 解码
   3. 输出
二. 播放器的实现
   1. 输入实现
   2. 解码线程实现
   3. 输出线程实现
三. 视频输出库
   1. SDL (多平台,支持硬件缩放)
   2. DirectX DirectDraw (win32平台,支持硬件缩放)
   3. OpenGL (多平台,支持硬件缩放)
   4. X11 (Linux/Unix)
   5. FrameBuffer (Linux, 无硬件缩放)
四. 音频输出
   1. OSS (Open Sound System for Linux)
   2. ALSA (Advanced Linux Sound Architecture)
   3. DirectSound (WIN32)
五. 音视频同步
   1. 以音频为基准同步视频
   2. 以视频为基准同步音频
   3. 同步于一个外部时钟
六. 截图
   1. 使用jpeglib保存成jpeg文件
   2. 使用libpng保存成png文件
七. YUV RGB 软件转换
八. 软件缩放

一. 播放器的流程

1. 输入 : 从文件或网络等读取原数据,如 x.avi, x.mov, rtsp://xxx, 对原数据进行解析,比如文件,首先要分析文件格式,从文件中取得音视频编码参数,视频时间长度等信息,然后要从其中取出音频编码数据和视频编码数据送到解 码部分,这里暂称这种编码源数据块为 packet。

2. 解码 : 初始化时,利用输入端从源数据中取得的信息调用不同的解码库初始化;然后接收输入端传送来的音视频编码数据,分别进行音频解码和视频解码,视频解码出来的 数据一般是 YUV 或 RGB 数据,这里暂称为 picture, 音频解码出来的数据是采样数据,是声卡可以播放的数据,这里暂称为 sample。 解码所得的数据接下来送到输出部分。

3. 输出 : 接收解码部分送来的 picture 和 sample 并显示。 视频显示一般使用某个图形库,如 SDL, Xlib, DirectDraw, OpengGL, FrameBuffer等, 音频输出是把 sample 写入系统的音频驱动,由音频驱动送入声卡播放, 可用的音频输出有 ALSA, OSS, SDL, DirectSound, WaveOut等。

二. 播放器的实现

推荐实现方案
一个audio_packet队列,一个video_packet队列,一个picture队列,一个sample队列
一个input线程,两个decode线程,两个output线程,一个UI控制线程

1. 输入实现
对 文件的解析,首先要了解文件的格式,文件格式一般称为文件容器。公开的文件格式,按格式协议读取分析就可以了,但像RMVB,SWF这种目前还不公开格式 的文件,就不好办,也是目前一般播放器的困难。一般的文件格式的解析libavformat库已经做了,只要使用它就行,下面给出示例代码段:

初始化:
static int avin_file_init(void)
{
    AVFormatParameters params, *ap = ?ms;
    err = av_open_input_file( &fmtctx, input_filename, NULL, 0, ap );
    if ( err < 0 )
    {
        av_log(NULL, AV_LOG_ERROR, "%d: init input from file error\n", __LINE__);
        print_error( input_filename, err );
        return -1;
    }

fmtctx->flags |= AVFMT_FLAG_GENPTS;

err = av_find_stream_info( fmtctx );
    if ( err < 0 )
    {
        av_log(NULL, AV_LOG_ERROR, "%d: init input from file error\n", __LINE__);
        print_error( input_filename, err );
        return -1;
    }

if (fmtctx->pb) fmtctx->pb->eof_reached = 0;
    dump_format( fmtctx, 0, input_filename, 0 );

....
}
读取packet:
while( 1 )
{
    AVPacket *pkt = NULL;
    pkt = av_malloc( sizeof(AVPacket) );
    ret = av_read_frame(fmtctx, pkt);
    
送出packet到解码部分:
    可以memcpy, 或用LinkList结构处理,如:
    push_to_video_packet_queue(pkt);
}

如果是自己的私有输入,比如移动电视的视频输入,代码如下,部分是伪代码:
while( 1 )
{
    your_parse_code();
    size = your_get_video_data(buf);

pkt = av_mallocz( sizeof(AVPacket) );
    x = av_new_packet( pkt, vret);
    memcpy( pkt->data, buf, size );
    pkt->pts = your_time;

push_to_video_packet_queue(pkt);
}

2. 解码线程实现
解码是个算法大课题,大多只能使用已有的解码库,如libavcodec,下面示例代码:
while ( 1 )
{
    AVPicture *picture;
    AVPacket *pkt = pop_from_video_packet_queue();
    AVFrame *frame = avcodec_alloc_frame();
    avcodec_decode_video(video_ctxp, frame, &got_picture, pkt->data, pkt->size);
    if ( got_picture )
    {
        convert_frame_to_picture( picture, frame );
        picture->pts = pkt->pts;
        push_to_picture_queue( picture );
    }
}
音频雷同

3. 输出线程实现

视 频输出要控制FPS,比如25帧每秒的视频,那么每一帧的显示时间要是1/25秒,但把一帧RGB数据写入显存用不了1/25秒的时间,那么就要控制,不 能让25帧的数据在0.1或0.2秒的时间内就显示完了,最简单的实现是在每显示一帧数据后,sleep( 1/fps - 显示用去的时间 )。

音 视频同步这个重要的工作也要在输出线程里完成。以音频为基准同步视频,以视频为基准同步音频,或与一个外部时钟同步,都是可行的方法,但以音频为基准同步 视频是最简单也最有效的方法。音频驱动只要设置好sample rate, sample size 和 channels 后, write 数据就会以此恒定的速度播放, 如果驱动的输出 buffer 满,则 write 就可以等待。

视频:
while( 1 )
{
    picture = pop_from_picture_queue();
    picture_shot( picture ); /* 截图 */
    vo->display( picture );
    video_pts = picture->pts;
    sync_with_audio(); /* 同步 */
    control_fps(); /* FPS */
}
音频:
while( 1 )
{
    sample = pop_from_sample_queue();
    ao->play( sample );
    now_pts = sample->pts;
}

三. 视频输出库

1. SDL (多平台,支持硬件缩放)

SDL(Simple DirectMedia Layer) is a cross-platform multimedia library designed to provide low level access to audio, keyboard, mouse, joystick, 3D hardware via OpenGL, and 2D video framebuffer.

其实SDL就是一个中间件,它封装了下层的OpenGL, FrameBuffer, X11, DirectX等给上层提供一个统一的API接口,使用SDL的优点是我们不必再为X11或DirectX分别做个视频输出程序了。

SDL可以直接显示YUV数据和RGB数据,一般解码得到的picture都是YUV420P格式的,不用做YUV2RGB的转换就可以直接显示,主要代码如下:

static int vo_sdl_init(void)
{
    ....
    screen = SDL_SetVideoMode(ww, wh, 0, flags);
    overlay = SDL_CreateYUVOverlay(dw, dh, SDL_YV12_OVERLAY, screen);
   ....
}

static void vo_sdl_display(AVPicture *pict)
{
    SDL_Rect rect;
    AVPicture p;

SDL_LockYUVOverlay(overlay);
    p.data[0] = overlay->pixels[0];
    p.data[1] = overlay->pixels[2];
    p.data[2] = overlay->pixels[1];
    p.linesize[0] = overlay->pitches[0];
    p.linesize[1] = overlay->pitches[2];
    p.linesize[2] = overlay->pitches[1];
    vo_sdl_sws( &p, pict ); /* only do memcpy */
    SDL_UnlockYUVOverlay(overlay);

rect.x = dx;
    rect.y = dy;
    rect.w = dw;
    rect.h = dh;
    SDL_DisplayYUVOverlay(overlay, &rect);
}

2. DirectX DirectDraw (win32平台,支持硬件缩放)

DirectX是window上使用较多的一种输出,也支持直接YUV或RGB显示,示例代码:

static int vo_dx_init(void)
{
    DxCreateWindow();
    DxInitDirectDraw();
    DxCreatePrimarySurface();
    DxCreateOverlay();
    DetectImgFormat();
}

static void vo_dx_display(AVPicture *pic)
{
    vfmt2rgb(my_pic, pic);
    memcpy( g_image, my_pic->data[0], my_pic->linesize[0] * height );
    flip_page();
}

3. OpenGL (多平台,支持硬件缩放)

OpenGL是3D游戏库,跨平台,效率高,支持大多数的显示加速,显示2D RGB数据只要使用glDrawPixels函数就足够了,同时禁用一些OpenGL管线操作效率更高,如:

glDisable( GL_SCISSOR_TEST );
    glDisable( GL_ALPHA_TEST );
    glDisable( GL_DEPTH_TEST );
    glDisable( GL_DITHER );

4. X11 (Linux/Unix)

X11 是Unix/Linux系统平台上的基本图形界面库,像普通的GTK,QT等主要都是建立在X11的基础之上。但X11的API接口太多,复杂,很不利于 开发,基本的GUI程序一般都会使用GTK,QT等,不会直接调用X11的API,这里只是为了效率。MPlyaer的libvo里有X11的完整使用代 码,包括全屏等功能。

static void vo_x11_display(AVPicture* pic)
{
    vfmt2rgb( my_pic, pic );
    Ximg->data = my_pic->data[0];
    XPutImage(Xdisplay, Xvowin, Xvogc, Ximg,
              0, 0, 0, 0, dw, dh);
    XSync(Xdisplay, False);
    XSync(Xdisplay, False);
}

5. FrameBuffer (Linux, 无硬件缩放)

FrameBuffer是Linux内核的一部分,提供一个到显存的存取地址的map,但没有任何加速使用。

static void vo_fb_display(AVPicture *pic)
{
    int i;
    uint8_t *src, *dst = fbctxp->mem;

vfmt2rgb( my_pic, pic );
    src = my_pic->data[0];

for ( i = 0; i < fbctxp->dh; i++ )
    {
        memcpy( dst, src, fbctxp->dw * (fbctxp->varinfo.bits_per_pixel / 8) );
        dst += fbctxp->fixinfo.line_length;
        src += my_pic->linesize[0];
    }
}

四. 音频输出

1. OSS (Open Sound System for Linux)

OSS是Linux下面最简单的音频输出了,直接write就可以。

static int ao_oss_init(void)
{
    int i;
    dsp = open(dsp_dev, O_WRONLY);
    if ( dsp < 0 )
    {
        av_log(NULL, AV_LOG_ERROR, "open oss: %s\n", strerror(errno));
        return -1;
    }
    i = sample_rate;
    ioctl (dsp, SNDCTL_DSP_SPEED, &i);
    i = format2oss(sample_fmt);
    ioctl(dsp, SNDCTL_DSP_SETFMT, &i);
    i = channels;
    if ( i > 2 ) i = 2;
    ioctl(dsp, SNDCTL_DSP_CHANNELS, &i);

return 0;
}

static void ao_oss_play(AVSample *s)
{
    write(dsp, s->data, s->size);
}

2. ALSA (Advanced Linux Sound Architecture)

ALSA做的比较失败,长长的函数名。

static void ao_alsa_play(AVSample *s)
{
    int num_frames = s->size / bytes_per_sample;
    snd_pcm_sframes_t res = 0;
    uint8_t *data = s->data;

if (!alsa_handle)
        return ;

if (num_frames == 0)
        return ;

rewrite:
    res = snd_pcm_writei(alsa_handle, data, num_frames);
    if ( res == -EINTR )
        goto rewrite;
    if ( res < 0 )
    {
        snd_pcm_prepare(alsa_handle);
        goto rewrite;
    }
    if ( res < num_frames )
    {
        data += res * bytes_per_sample;
        num_frames -= res;
        goto rewrite;
    }
}

3. DirectSound (WIN32)

MS DirectX的一部分,它的缺点是不如Linux里面的OSS或ALSA那样,在没有sample写入的时候,自动 silent,DirectSound在播放过程中,当没有sample数据送入输出线程时,它总是回放最后0.2或0.5秒的数据。由于只是最近移植 DawnLightPlayer才使用起Windows,不太了解其机制。

static void dsound_play(AVSample *s)
{
    int wlen, ret, len = s->size;
    uint8_t *data = s->data;

while ( len > 0 )
    {
        wlen = dsound_getspace();
        if ( wlen > len ) wlen = len;
        ret = write_buffer(data, wlen);
        data += ret;
        len -= ret;
        usleep(10*1000);
    }
}

五. 音视频同步

1. 以音频为基准同步视频

视频输出线程中如下处理:
    start_time = now();
    ....
    vo->display( picture );
    last_video_pts = picture->pts;
    end_time = now();
    rest_time = end_time - start_time;
    av_diff = last_audio_pts - last_video_pts;
    if ( av_diff > 0.2 )
    {
        if ( av_diff < 0.5 ) rest_time -= rest_time / 4;
        else rest_time -= rest_time / 2;
    }
    else if ( av_diff < -0.2)
    {
        if ( av_diff > -0.5 ) rest_time += rest_time / 4;
        else rest_time += rest_time / 2;
    }
    if ( rest_time > 0 )
        usleep(rest_time);

2. 以视频为基准同步音频

3. 同步于一个外部时钟

六. 截图

截图可以在解码线程做,也可以在输出线程做,见前面的输出线程部分。只要在display前把picture保存起来即可。一般加一些编码,如保存成 PNG 或 JPEG 格式。

1. 使用jpeglib保存成jpeg文件

static void draw_jpeg(AVPicture *pic)
{
    char fname[128];
    struct jpeg_compress_struct cinfo;
    struct jpeg_error_mgr jerr;
    JSAMPROW row_pointer[1];
    int row_stride;
    uint8_t *buffer;

if ( !po_status )
        return ;

vfmt2rgb24(my_pic, pic);
    buffer = my_pic->data[0];

#ifdef __MINGW32__
    sprintf(fname, "%s\\DLPShot-%d.jpg", get_save_path(), framenum++);
#else
    sprintf(fname, "%s/DLPShot-%d.jpg", get_save_path(), framenum++);
#endif
    fp = fopen (fname, "wb");
    if (fp == NULL)
    {
        av_log(NULL, AV_LOG_ERROR, "fopen %s error\n", fname);
        return;
    }
    cinfo.err = jpeg_std_error(&jerr);
    jpeg_create_compress(&cinfo);
    jpeg_stdio_dest(&cinfo, fp);

cinfo.image_width = width;
    cinfo.image_height = height;
    cinfo.input_components = 3;
    cinfo.in_color_space = JCS_RGB;

jpeg_set_defaults(&cinfo);
    cinfo.write_JFIF_header = TRUE;
    cinfo.JFIF_major_version = 1;
    cinfo.JFIF_minor_version = 2;
    cinfo.density_unit = 1;
    cinfo.X_density = jpeg_dpi * width / width;
    cinfo.Y_density = jpeg_dpi * height / height;
    cinfo.write_Adobe_marker = TRUE;

jpeg_set_quality(&cinfo, jpeg_quality, jpeg_baseline);
    cinfo.optimize_coding = jpeg_optimize;
    cinfo.smoothing_factor = jpeg_smooth;
    if ( jpeg_progressive_mode )
    {
        jpeg_simple_progression(&cinfo);
    }
    jpeg_start_compress(&cinfo, TRUE);

row_stride = width * 3;
    while (cinfo.next_scanline < height)
    {
        row_pointer[0] = &buffer[cinfo.next_scanline * row_stride];
        (void)jpeg_write_scanlines(&cinfo, row_pointer, 1);
    }

jpeg_finish_compress(&cinfo);
    fclose(fp);
    jpeg_destroy_compress(&cinfo);

return ;
}

2. 使用libpng保存成png文件

static void draw_png(AVPicture *pic)
{
    int k;
    png_byte *row_pointers[height]; /* GCC C99 */

if ( init_png() < 0 )
    {
        av_log(NULL, AV_LOG_ERROR, "draw_png: init png error\n");
        return ;
    }

vfmt2rgb24( my_pic, pic );

for ( k = 0; k < height; k++ )
        row_pointers[k] = my_pic->data[0] + my_pic->linesize[0] * k;

png_write_image(png.png_ptr, row_pointers);

destroy_png();
}

七. YUV RGB 转换

YUV 与RGB的转换和缩放,一般在低端设备上,要有硬件加速来做,否则CPU吃不消。在如今的高端PC上,可以使用软件来做,libswscale库正为此而 来。libswscale针对X86 CPU已经做了优化,如使用 MMX, SSE, 3DNOW 等 CPU 相关的多媒体指令。

static int vfmt2rgb(AVPicture *dst, AVPicture *src)
{
    static struct SwsContext *img_convert_ctx;

img_convert_ctx = sws_getCachedContext(img_convert_ctx,
                      width, height, src_pic_fmt,
                      width, height, my_pic_fmt, SWS_X, NULL, NULL, NULL);

sws_scale(img_convert_ctx, src->data, src->linesize,
              0, width, dst->data, dst->linesize);

return 0;
}

比如从 YUV420P 到 RGB24 的转换,只要设置

src_pic_fmt = PIX_FMT_YUV420P ;
my_pic_fmt = PIX_FMT_RGB24 ;

八. 软件缩放

软件缩放就可以使用上述的 libswscale 库,调用代码基本一样,只是改一下目标picture的width和height,如放大两倍:

static int zoom_2(AVPicture *dst, AVPicture *src)
{
    static struct SwsContext *img_convert_ctx;

img_convert_ctx = sws_getCachedContext(img_convert_ctx,
                      width, height, src_pic_fmt,
                      width*2, height*2, my_pic_fmt, SWS_X, NULL, NULL, NULL);

sws_scale(img_convert_ctx, src->data, src->linesize,
              0, width*2, dst->data, dst->linesize);

return 0;
}

时间: 2024-12-10 23:06:34

linux下播放器的设计和开发的相关文章

在Linux下播放与录制电视

最近,在自己的Debian 7.2 64位系统上成功安装了圆刚AverMedia C725B视频卡驱动.于是,可以使用mplayer与mencoder来看电视与录节目了.其中,用于播放电视的命令如下: mplayer tv:// -tv driver=v4l2:device=/dev/video0:norm=PAL:alsa:adevice=hw.2,0:amode=1:audiorate=48000:forceaudio:volume=100:immediatemode=0:normid=8:

linux 下安装 mysql 并配置 python 开发环境

1.安装 mysql ,安装过程中将提示设置 root 用户的密码,默认可以设置为 rootadmin . $ sudo apt-get install mysql-server 2.安装 mysql 开发工具(不安装时,安装 MySQL-python 提示错误 "mysql_config not found"). $ sudo apt-get install libmysqld-dev 3.安装 python 的 mysql 库 MySQL-python (首先安装 python-d

ios开发:一个音乐播放器的设计与实现

github地址:https://github.com/wzpziyi1/MusicPlauer 这个medo,关于歌曲播放的主要功能都实现了的.下一曲.上一曲,暂停,根据歌曲的播放进度动态滚动歌词,将当前正在播放的歌词放大显示,拖动进度条,歌曲跟着变化,并且使用Time Profiler进行了优化,还使用XCTest对几个主要的类进行了单元测试. 已经经过真机调试,在真机上可以后台播放音乐,并且锁屏时,显示一些主要的歌曲信息. 首页: 歌曲内部播放: 当拖动小的进度条的时候,歌曲也会随之变化.

VUE项目实现音乐播放器(四)------- 设计播放进度条 + 播放控制按钮

2020.3.31 9:18 好的,早上好各位,今天我们来进行一个很炫酷的页面开发——播放器控制页面( src\components\Play.vue ),如下图: 我们可以看到,该页面有很多元素组成,歌曲的封面.左上角的页面隐藏按钮,中间的播放进度条.歌词栏.下方的播放控制按钮.右下角的显示播放列表按钮,这些元素完美的结合在一起,整个页面有没有一种很高大上的感觉!好了,让我们来亲自动手实现它吧! 1. 歌曲封面&隐藏按钮 首先通过 getters 获取到歌曲的封面: computed: { i

基于Linux的OJ系统的设计与开发(一)

最近在研究基于linux的OJ系统,然后想自己写一系列文章记录自己这段时间的学习成果. 首先,从原理上讲,OJ功能实现并不难,最主要解决的是安全性问题.总结一下,而安全性方面问题主要是用户可能提交恶意不友好的代码.关于如何过滤这些不安全的代码,我从网上收集整理了许多资料,大体上思路如下: 先说错误的做法: 1.所有的字符串过滤都是不靠谱儿的,坑人坑自己,C语言强大的宏几乎没有绕不过的字符串过滤,而且误伤也是很常见的,比如,你在程序里要是不小心定义一个叫做fork的变量,那么你的程序别指望可以AC

Linux下日志系统的设计

简介:通过日志系统的设计,将多台主机上的日志统一发送到一台服                                器,日志服务器自动将日志记录到mysql数据库,远程通过web方式调用数据库查看日志(使用loganalyzer解决方案,基于php). 在linux系统下,使用apache做web客户端,mysql数据库,php为web及后台数据的调用,统称为lamp. 日志的种类:/var/log/secure 身份验证有关信息的日志 /var/log/maillog 邮件相关的日志

Linux下串口与工业协议的开发

1.串口通信原理 串口通信定义 串口通信:数据的串行传送方式.串口通信可分为同步通信与异步通信. 同步通信:按照软件识别同步字符来实现数据的发送和接收. 将许多字符组成一个信息组进行发送 要求发送时钟和接收时钟保持严格的同步 效率相对高,对双发时钟的误差要求也高 异步通信:利用字符的再同步技术的通信方式. 按字符一个一个进行传输 每传输一个字符,就用起始位来通知对方,以此来重新核对收发双方同步 可靠性较高,但效率较低 同步通信按帧为单位传输,异步通信按字符为单位传输.(数据链路层) 9针串口管脚

Linux下C ,C ++, Qt开发环境

Linux 发行版的选择 尽量优选Ubuntu, 用户开发者多. 而且较大的公司的开源项目测试Linux平台也是优先Ubuntu. openSUSE也不错.但是大环境还是ubuntu. 建议安装时候选英文版.慢慢习惯就好了. 安装常用的开发工具 先换源. 清华或者科大不然速度不给力. (software & update中设置) sudo apt update sudo apt upgrade sudo apt install gcc g++ -y sudo apt install build-

驳Linux不娱乐 堪比Win平台中十款播放器

驳Linux不娱乐 堪比Win平台中十款播放器 1.VLC多媒体播放器     VLC多媒体播放器(最初命名为VideoLAN客户端)是VideoLAN计划的多媒体播放器.它支持众多音频与视频解码器及档案格式,并支持 DVD.VCD的播放及各类串流协议.它亦能作为unicast和multicast的串流服务器在IPv4以及IPv6的高速网络连线下使用.它融合了 FFmpeg出品的解码器与libdvdcss程序库,这更使其增添了播放多媒体档案及加密DVD影碟的功能. VLC多媒体播放器具有跨平台的