📄 linux下的tv播放器.txt
字号:
今天就对我实现的linux下的tv播放器进行一个阶段性的总结吧,目前我的这个tv测试程序已经能够实现流畅播放电视,在更改视频帧缓冲设置的情况下录制效果也还不错,并且实现了频道调整和电视频道回放的功能;另外,底层电视库中也增加了亮度、对比度、饱和度、色调调整的代码,以便将来在上层GUI界面中添加对应的设置功能;
这次我先比较详细的说明电视播放和录制的原理和关键代码的实现,两个部分就不拆开了,算作是晚交文章的一个惩罚吧,录制部分就算作是补偿的那一篇啦。
要想弄清楚电视播放和录制的原理,首先得搞清楚电视播放和录制时,数据帧流的流向和数据的存储是如何完成的。
下面我就先介绍一下电视播放时视频帧流的流向:
从视频设备/dev/video采集到得视频数据帧
-->
送到buffer存储
-->
1、(经过YUV420P到RGB32得转换)显示;
2、如果是录制状态,则对视频帧进行压缩,准备存储;同时要从音频设备中/dev/dsp中读取音频数据,我这里采用得是压缩存储,则需要经过mp3压缩,准备存储;
-->
1、显示部分,RGB32格式得视频帧可以直接使用QT得函数进行显示,如果你是采用SDL进行显示,那前面得RGB32格式转换都可以省略了,SDL可以直接显示YUV格式得视频帧;
2、录制状态中,在选定存储得文件格式(比如AVI)后,就进行音频和视频得数据存储了;
电视的播放和录制我想到这里你会有一个大概的数据流的概念了,这样,在以后的编码中就是以此数据流为基础,充分利用计算机的各种已有东西,进行编码实现这种理念;
下面我开始结合具体代码和文件结构进行详细说明:
首先分析几个技术难点,对于视频帧的采集、显示、压缩存储,在录制状态下这几样是同时进行的,这种并行计算在计算机中可以采用线程来实现,(由于进程消耗资源比较大,不可取),这样加上主GUI线程,在录制状态下,同时会有四个线程在运行;
其次,对 于数据帧的存储,我这里采用的方法是使用一个内存视频帧缓冲循环队列,视频帧的显示和录制都是从这个队列当中读取视频帧,而采集视频帧的线程则是将视频帧 写入这个队列中;当然了,这期间需要注意的是尽量使用指针而避免采用大量的内存拷贝操作,以便提高性能;至于具体如何操作,后面会进行详述;
最后,在视频进行全屏的实现过程中,至少要经过三个步骤--对视频帧进行全屏大小的转换,进行YUV到RGB32的格式转换,最后进行GUI界面刷屏显示;这三个步骤中前两步是比较耗资源的,我现在实现的tv测试程序,仍然不能够很好的解决电视在进行全屏播放的视频帧刷新缓慢问题,这也是当前的一个突出的难点;
有关这个问题的解决方案,我目前是打算参考mplayer的实现全屏的方法,只是mplayer全屏实现代码太散,目前还没有找到;
好,下面我先说说我的测试程序的文件夹结构,整个程序包括以下几个文件夹--bin、common、doc、gui、hdware、interface、image,最主要的是gui、hdware和interface三个文件夹中的东西,顾名思义,gui主要包含了和界面显示以及tv视频帧显示的代码部分,而hdware则是与硬件相关比较紧密的部分,我在这里放了电视的底层实现库,interface则是gui和hdware之间的接口部分,主要是线程的C++实现和缓冲类以及需要的几个线程类的代码;
文件结构如下图所示:
// ------------------------------------------------
# ls gui
Qtgui // 之所以有这个qt的gui文件夹,是因为以后可能还需要写其他界面的界面部分,所以这里留出扩展的余地,以方便将来进行扩展;
# find gui/qtgui
gui/qtgui/
gui/qtgui/mainwindow.cpp // 主窗口类文件;
gui/qtgui/mainwindow.h
gui/qtgui/mainwindow.moc
gui/qtgui/tvwindow.moc // 显示tv的窗口类;
gui/qtgui/tvwindow.cpp
gui/qtgui/tvwindow.h
gui/qtgui/mainwindow.o
gui/qtgui/tvwindow.o
gui/qtgui/main.cpp // 主函数;
gui/qtgui/Makefile
gui/qtgui/qttv.o
gui/qtgui/qtwindow.a // 打包成库;
// ------------------------------------------------
# ls hdware
video
# ls hdware/video/tv
hdware/video/tv/
hdware/video/tv/tv.bak.c
hdware/video/tv/tv.c // tv底层实现库;
hdware/video/tv/tv.h
hdware/video/tv/tv.o
hdware/video/tv/Makefile
hdware/video/tv/tv.a // 打包成库;方便以后将这部分单独拿出来独立成库;
// ------------------------------------------------
# find interface
interface/
interface/thread
interface/thread/Makefile
interface/thread/mthread.cpp // 一个C++封装的线程类,使用方法类似java中的线程类;
interface/thread/mthread.o
interface/thread/thread.a // 打包成库;
interface/thread/mthread.h
interface/Makefile
interface/tvgrabthread.cpp // 从mthread类继承,实现采集视频功能;
interface/tvgrabthread.h
interface/tvgrabthread.o
interface/tvrecordthread.o
interface/tvrecordthread.cpp // 从mthread类继承,实现录制视频功能;
interface/tvrecordthread.h
interface/tvthread.a // 打包成库;
interface/buffer
interface/buffer/videobuffer.o
interface/buffer/videobuffer.h
interface/buffer/Makefile
interface/buffer/buffer.a // 打包成库;
interface/buffer/videobuffer.cpp // 视频帧缓冲类;
其实看了这些,我想你对具体那一部分是做什么的,以及如何编写已经有了大概的认识了,下面我会对一些关键点进行具体的说明;
一共两个关键点--播放和录制;
具体涉及到的技术点一共有四个,分别是:采集视频帧线程、显示视频帧线程、录制视频帧线程、视频帧缓冲队列;
下面我就分别针对这几个技术关键点进行详细说明;
电视播放--
采集视频帧线程
视频帧采集采用线程实现的原因是为了能够提高GUI界面的响应速度,这一点我想大家都是毋庸置疑的。
详细描述:
此线程主要完成从Linux下视频设备/dev/video中读取视频帧,并将其保存在视频帧队列中(详情请看下一条说明);本线程是继承自一个线程类, 这个线程类是自己编写的,特性和使用方法都可以参考java中的线程类;因此,本线程最关键的地方也就是重新实现线程的run方法,这里我将run中关键 代码贴出来,并把加上详细注释;
Void LtvGrabThread::run(){
//
AVFrame *pf_frameYUV420P = NULL;
// Allocate video frame
pm_frame = avcodec_alloc_frame();
pm_frame_YUV420P = pm_frame;
// Read frames
while ( m_tv_playing_flag ){
// while tv is playing, read one frame from packets;
if ( av_read_frame (pm_format_context, &f_packet) < 0 ){
continue;
}
// Is this a packet from the video stream?
if (f_packet.stream_index == m_video_stream)
{
// Decode video frame
avcodec_decode_video (pm_codec_context, pm_frame,
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -