目录
一、FFMPEG 视频 API 的使用
1.1 介绍
主要做视频的解码 --- 视频的播放
视频在最终转换为 RGB 格式之后,需要填充在屏幕上,然后完成播放
视频解码,然后借助工具 --- LVGL,让图片控件切换显示的图像
FFMPEG、LVGL
这是一个 FFMPEG 可以实现解码的程序需要的所有 API 的解析
1.2 整体编程过程
获取核心上下文指针
之前的编程中,对于结构体都是叫核心结构体
但是在 FFMPEG 中,他叫上下文 --- context
函数头文件
#include "libavformat/avformat.h"
函数功能
创建一个AVFormatcontext,申请内存空间,并初始化一些初始变量
函数原型
AVFormatContext *avformat_alloc_context(void)
打开输入流文件
函数头文件
#include "libavformat/avformat.h"
函数功能
打开一个文件并解析。可解析的内容包括:视频流、音频流、视频流参数、音频流参数、视频帧索引
函数原型
int avformat_open_input(AVFormatContext **ps, const char *url, ff_const59 AVInputFormat *fmt, AVDictionary **options);
函数参数
AVFormatContext **ps:格式化的上下文。要注意,如果传入的是一个AVFormatContext*的指针,则该空间须自己手动清理,若传入的指针为空,则FFmpeg会内部自己创建。
const char *url:传入的地址。支持http,RTSP,以及普通的本地文件。地址最终会存入到AVFormatContext结构体当中。
AVInputFormat *fmt:指定输入的封装格式。一般传NULL,由FFmpeg自行探测。
AVDictionary **options:其它参数设置。它是一个字典,用于参数传递,不传则写NULL。参见:libavformat/options_table.h,其中包含了它支持的参数设置。
函数返回值
成功返回0,失败返回负数
获取输入流
函数功能
查找格式和索引。有些早期格式它的索引并没有放到头当中,需要你到后面探测,就会用到此函数
函数原型
int avformat_find_stream_info(AVFormatContext *ic, AVDictionary **options);
函数参数
fmt_ctx:AVFormatContext结构体指针,表示媒体文件的格式上下文,其中包含已经打开的媒体文件的信息和媒体文件中每个音视频流的信息。
其中成员变量streams[0]表示视频流,streams[1]表示音频流
options:AVDictionary结构体指针,用于传递选项。目前已经不使用,传NULL即可。
函数返回值
成功返回>=0的数
获取编码器
FFmpeg提供两种方式查找解码器,通过codecId查找avcodec_find_decoder()与通过名字查找avcodec_find_decoder_by_name()。同样地,也提供两种方式查找编码器,通过codecId查找avcodec_find_encoder()与通过名字查找avcodec_find_encoder_by_name()。源码位于libavcodec/allcodecs.c中
AVCodec *avcodec_find_decoder(enum AVCodecID id)
在AVFormatcontext上下文指针的视频流下的codec中的codec_id有当前视频的编码器格式
项目中可以使用AV_CODEC_ID_H264
编解码器列表来自libavcodec/allcodecs.c,有声明全局变量
成功返回需要的解码器指针
初始化解码器
函数功能
初始化一个视音频编解码器的AVCodecContext
函数原型
int avcodec_open2(AVCodecContext *avctx, const AVCodec *codec, AVDictionary **options)
函数参数
avctx:需要初始化的AVCodecContext。
codec:输入的AVCodec
options:一些选项。例如使用libx264编码的时候,“preset”,“tune”等都可以通过该参数设置。
函数返回值
成功返回0,失败返回负数
申请输出流指针
函数功能
void *av_malloc(size_t size)主要作用是根据传入的size大小来调用c的malloc函数来开辟一块内存空间并且返回指向开辟内存空间的指针
函数原型
void *av_malloc(size_t size)
函数参数
获取显示数据空间大小
头文件
#include "libavutil/imgutils.h"
函数功能
通过指定像素格式、图像宽、图像高来计算所需的内存大小
函数原型
int av_image_get_buffer_size(enum AVPixelFormat pix_fmt, int width, int height, int align)
函数参数
AVPixelFormat:
输出数据的格式AV_PIX_FMT_RGB24 或AV_PIX_FMT_ABGR
width:
输出数据的宽度,根据自己的情况而定
height:
输出数据的高度,根据自己的情况而定
align:此参数是设定内存对齐的对齐数,也就是按多大的字节进行内存对齐。比如设置为1,表示按1字节对齐,那么得到的结果就是与实际的内存大小一样。再比如设置为4,表示按4字节对齐。也就是内存的起始地址必须是4的整倍数
申请输出显示空间
函数功能
分配AVFrame并将其字段设置为默认值。主要该函数只分配AVFrame的空间,它的data字段的指定的buffer需要其它函数分配
函数原型
AVFrame *av_frame_alloc(void)
函数参数
AVFrame结构体,它比较重要的字段有:
data[AV_NUM_DATA_POINTERS]:存放解码后的原始媒体数据的指针数组。
对于视频数据而言,planar(YUV420)格式的数据,Y、U、V分量会被分别存放在data[0]、data[1]、data[2]……中。packet格式的数据会被存放在data[0]中。
对于音频数据而言,data数组中,存放的是channel的数据,例如,data[0]、data[1]、data[2]分别对应channel 1,channel 2 等。
linesize[AV_NUM_DATA_POINTERS]:视频或音频帧数据的行宽数组。
对video而言:每个图片行的字节大小。linesize大小应该是CPU对齐的倍数,对于现代pc的CPU而言,即32或64的倍数。
对audio而言:代表每个平面的字节大小。只会使用linesize[0]。 对于plane音频,每个通道 的plane必须大小相同。
**extended_data:对于视频数据:只是简单的指向data[]。
对于音频数据:planar音频,每个通道都有一个单独的数据指针,而linesize [0]包含每个通道缓冲区的大小。 对于packet音频,只有一个数据指针,linesize [0]包含所有通道的缓冲区总大小。
key_frame:当前帧是否为关键帧,1表示是,0表示不是。
pts:以time_base为单位的呈现时间戳(应向用户显示帧的时间)
绑定输出流和输出显示空间
函数功能
根据指定的图像参数(显示数据)和提供的数组设置输出数据指针和行大小
函数原型
int av_image_fill_arrays(uint8_t **dst_data, int *dst_linesize, const uint8_t *src, enum AVPixelFormat pix_fmt, int width, int height, int align)
函数参数
dst_data:需要开辟空间的数据指针
AVFrame的成员data
dst_linesize: dst_data中要填充的图像的行大小
AVFrame的成员linesize
*src: 缓冲区将包含或包含实际图像数据,可以为NULL
pix_fmt: 显示图像的像素格式
width:申请src内存时指定的宽度
height:申请scr内存时指定的高度
align:申请src内存时指定的对齐字节数
函数返回值
成功返回大于0的数,失败返回负数
(a)计算所需内存大小av_image_get_bufferz_size()
(b)按计算的内存大小申请所需内存 av_malloc()
(c)对申请的内存进行格式化av_image_fill_arrays()
申请格式转换上下文
函数功能
获取一个上下文指针
函数头文件
#include "libswscale/swscale.h"
函数原型
struct SwsContext *sws_getContext(int srcW, int srcH, enum AVPixelFormat srcFormat, int dstW, int dstH, enum AVPixelFormat dstFormat, int flags, SwsFilter *srcFilter, SwsFilter *dstFilter, const double *param)
函数参数
参数1:被转换源的宽
AVFormatContext的视频流的codecpar中的width
参数2:被转换源的高
AVFormatContext的视频流的codecpar中的height
参数3:被转换源的格式,以直接用枚举的代号表示eg:AV_PIX_FMT_YUV420P这些枚举的格式在libeg:YUV、RGB……(枚举格式,也可avutil/pixfmt.h中列出)
AVFormatContext的视频流的codec中的pix_fmt
参数4:转换后指定的宽
参数5:转换后指定的高
参数6:转换后指定的格式
参数7:转换所使用的算法
#define SWS_FAST_BILINEAR 1
#define SWS_BILINEAR 2
#define SWS_BICUBIC 4
#define SWS_X 8
#define SWS_POINT 0x10
#define SWS_AREA 0x20
#define SWS_BICUBLIN 0x40
#define SWS_GAUSS 0x80
#define SWS_SINC 0x100
#define SWS_LANCZOS 0x200
#define SWS_SPLINE 0x400
参数8:NULL参数9:NULL参数10:NULL
申请输入流指针
函数功能
av_packet_alloc分配AVPacket以后,调用av_init_packet对AVPacket的成员变量进行初始化赋值
函数原型
AVPacket *av_packet_alloc(void)
读取一帧数据
函数功能
读取码流中的音频若干帧或者视频一帧
函数原型
int av_read_frame(AVFormatContext *s, AVPacket *pkt)
函数参数
AVFormatContext *s // 文件格式上下文,输入的AVFormatContext
AVPacket *pkt //这个值不能传NULL,必须是一个空间,输出的AVPacket
函数返回值
成功返回0,到达结尾或者失败返回负数
发送给解码器
函数功能
将一个packet放入到队列中等待解码。并不是一个packet,就代表一个frame,解码操作是在该函数中进行的。完成此操作后,解码后的数据放在avctx->internal->buff_frame中
函数原型
int avcodec_send_packet(AVCodecContext *avctx, const AVPacket *avpkt)
函数参数
AVCodecContext *avctx:第一个参数与旧的接口一致,是视频解码的上下文,包含解码器。
const AVPacket *avpkt: 编码的音视频帧数据
从解码器读取一帧数据
函数功能
进行一个拷贝的操作,将avctx中解码后的数据拷贝给avframe
函数原型
int avcodec_receive_frame(AVCodecContext *avctx, AVFrame *frame)
函数参数
AVCodecContext *avctx:第一个参数视频解码的上下文,与上面接口一致。
AVFrame *frame:解码后的视频帧数据。
格式转换
函数功能
对图像的大小进行缩放。转换图像格式跟颜色空间,例如把YUYV422转成RGB24。转换像素格式的存储布局,例如把 YUYV422 转成 YUV420P ,YUYV422 是 packed 的布局,YUV 3 个分量是一起存储在 data[0] 里面的。而 YUV420P 是 planner 的布局,YUV 分别存储在 data[0] ~ data[2]。
函数原型
int sws_scale(struct SwsContext *c, const uint8_t *const srcSlice[],
const int srcStride[], int srcSliceY, int srcSliceH,
uint8_t *const dst[], const int dstStride[])
函数参数
第一个参数即是由 sws_getContext 所取得的参数。
第二个 src 及第六个 dst 分别指向input 和 output 的 buffer。
input指数据来源,即为avcodec_receive_frame函数第二个参数中的data成员
output指数据转换后存放的位置,需要额外的AVFrame指针
第三个srcStride 及第七个 dstStride 分別指向 input 及 output 的 stride;如果不知道什么是 stride,姑且可以先把它看成是每一列的 byte 数。
即为AVFrame的linesize成员
第四个srcSliceY,就注释的意思来看,是指第一列要处理的位置;从头处理,所以直接填0。想知道更详细说明的人,可以参考swscale.h 的注释。
第五个srcSliceH指的是 source slice 的高度。
即为AVFrame的height成员
显示在屏幕上
在播放视频之前加一张图片,主要是为了提供一个更好的用户体验,避免空白或闪烁的显示效果,确保界面在视频播放开始时具有良好的视觉过渡。
1.3 参数扩展
资源的释放
avformat_close_input
avformat_free_context
数据格式转换
void rgb_to_argb(uint8_t * rgbdata,uint8_t * argbdata,int w,int h)
{
int i =0,j=0;
for(i = 0 ; i < h;i++)
{
for(j = 0; j<w;j++)
{
argbdata[j*4+i*w*4 + 3] = 0xFF;//透明度
argbdata[j*4+i*w*4 + 0] = rgbdata[j*3+i*w*3 + 2];
argbdata[j*4+i*w*4 + 1] = rgbdata[j*3+i*w*3 + 1];
argbdata[j*4+i*w*4 + 2] = rgbdata[j*3+i*w*3 + 0];
}
}
}
AVFormatcontext
struct AVInputFormat *iformat;//输入数据的封装格式。仅解封装用,由avformat_open_input()设置。
struct AVOutputFormat *oformat;//输出数据的封装格式。仅封装用,调用者在avformat_write_header()之前设置。
AVIOContext *pb;// I/O上下文。
解封装:由用户在avformat_open_input()之前设置(然后用户必须手动关闭它)或通过avformat_open_input()设置。
封装:由用户在avformat_write_header()之前设置。 调用者必须注意关闭/释放IO上下文。
unsigned int nb_streams;//AVFormatContext.streams中元素的个数。
AVStream **streams;//文件中所有流的列表。
char filename[1024];//输入输出文件名。
int64_t start_time;//第一帧的位置。
int64_t duration;//流的持续时间
int64_t bit_rate;//总流比特率(bit / s),如果不可用则为0。
int64_t probesize;
//从输入读取的用于确定输入容器格式的数据的最大大小。仅封装用,由调用者在avformat_open_input()之前设置。
AVDictionary *metadata;//元数据
AVCodec *video_codec;//视频编解码器
AVCodec *audio_codec;//音频编解码器
AVCodec *subtitle_codec;//字母编解码器
AVCodec *data_codec;//数据编解码器
int (*io_open)(struct AVFormatContext *s, AVIOContext **pb, const char *url, int flags, AVDictionary **options);
//打开IO stream的回调函数。
void (*io_close)(struct AVFormatContext *s, AVIOContext *pb);
//关闭使用AVFormatContext.io_open()打开的流的回调函数。
AVStream
int index; 标识该音频/视频流
int id; 流的标识,依赖于具体的容器格式。解码libavformat,编码由用户设置,用户不设置则使用libavformat
AVCodecContext *codec; 流对应与AVCodecContext结构,调用avformat_open_input设置
AVRational time_base; 表示帧时间戳的基本单位。通过该值可以把PTS,DTS转化为真正的时间,其他结构体中也有这个字段,只有AVStream 中的time_base为真正的时间。
int64_t start_time; 流的起始时间,以流的基准时间为单位
int64_t duration; 流的持续时间,如果源文件未指定持续时间,但指定了比特率,则将根据比特率和文件大小估计该值。
int64_t nb_frames; 流中帧的数据,为0或者已知
AVDictionary *metadata; 元数据
AVRational sample_aspect_ratio; 样本长宽比
AVRational avg_frame_rate; 评价帧率,解封装时:在创建流时设置或者在avformat_find_stream_info()中设置;封装时调用avformat_write_header()设置
AVCodecParameters *codecpar; 编解码器参数
int64_t first_dts; 第一个dts
int64_t cur_dts; 当前dts
int probe_packets; 编码器用户probe的包的个数
int codec_info_nb_frames; 在avformat_find_stream_info()期间已经解封装的帧数
AVPacket attached_pic;附带的一些图片
int request_probe; 流探测状态,-1表示完成,0表示没有探测请求,rest执行探测
int skip_to_keyframe; 是否丢弃所有内容直接跳到关键帧
int skip_samples;在下一个数据表解码的帧开始要跳过的采样数
int64_t start_skip_samples;从流的开始要跳过的采样的数量
int64_t first_discard_sample; 如果不是0,从流中丢弃第一个音频的样本
int nb_decoded_frames; 内部解码的帧数
int64_t pts_reorder_error[MAX_REORDER_DELAY+1];
uint8_t pts_reorder_error_count[MAX_REORDER_DELAY+1]; 内部数据从pts到dts
int64_t last_dts_for_order_check; 内部数据用于分析dts和探测mpeg流的错误
uint8_t dts_ordered;
uint8_t dts_misordered;
AVRational display_aspect_ratio; 显示的宽高比
AVCodecContext
enum AVMediaType codec_type; 编解码器的类型
const struct AVCodec *codec; 编解码器,初始化后不可更改
enum AVCodecID codec_id; 编解码器的id
int64_t bit_rate; 平均比特率
uint8_t *extradata; int extradata_size; 针对特定编码器包含的附加信息
AVRational time_base; 根据该参数可以将pts转化为时间
int width, height; 每一帧的宽和高
int gop_size; 一组图片的数量,编码时用户设置,解码时不使用
enum AVPixelFormat pix_fmt; 像素格式,编码时用户设置,解码时可由用户指定,但是在分析数据会被覆盖用户的设置
int refs; 参考帧的数量
enum AVColorSpace colorspace; YUV色彩空间类型
enum AVColorRange color_range; MPEG JPEG YUV范围
int sample_rate; 采样率 仅音频
int channels; 声道数(音频)
enum AVSampleFormat sample_fmt; //采样格式
int frame_size; 每个音频帧中每个声道的采样数量
int profile;配置类型
int level;级别
AVCodecParameters
AVCodec
const char *name; //编解码器名字
const char *long_name; //编解码器全名
enum AVMediaType type; //编解码器类型
enum AVCodecID id; //编解码器ID
const AVRational *supported_framerates; //支持帧率(视频)
const enum AVPixelFormat *pix_fmts; //支持像素格式(视频)
const int *supported_samplerates; //支持音频采样率(音频)
const enum AVSampleFormat *sample_fmts; //支持采样格式(音频)
const uint64_t *channel_layouts; //支持声道数(音频)
const AVClass *priv_class; //私有数据
AVPacket
AVPacket中的字段可用分为两部分:数据的缓存及管理,关于数据的属性说明。
关于数据的属性有以下字段:
pts 显示时间戳
dts 解码时间戳
stream_index Packet所在stream的index
flats 标志,其中最低为1表示该数据是一个关键帧
duration 数据的时长,以所属媒体流的时间基准为单位
pos 数据在媒体流中的位置,未知则值为-1
convergence_duration 该字段已被deprecated,不再使用
数据缓存,AVPacket本身只是个容器,不直接的包含数据,而是通过数据缓存的指针引用数据。AVPacket中包含有两种数据
data 指向保存压缩数据的指针,这就是AVPacket实际的数据。
side_data 容器提供的一些附加数据
buf 是AVBufferRef类型的指针,用来管理data指针引用的数据缓存的,其使用在后面介绍
AVFrame
typedef struct AVFrame {
#define AV_NUM_DATA_POINTERS 8
uint8_t *data[AV_NUM_DATA_POINTERS]; // 存放媒体数据的指针数组
int linesize[AV_NUM_DATA_POINTERS]; // 视频或音频帧数据的行宽
uint8_t **extended_data; // 音频或视频数据的指针数组。
int width, height; // 视频帧的款和高
/**
* number of audio samples (per channel) described by this frame
*/
int nb_samples; // 当前帧的音频采样数(每个通道)
int format; // 视频帧的像素格式,见enum AVPixelFormat,或音频的采样格式,见enum AVSampleForma
int key_frame; // 当前帧是否为关键帧,1表示是,0表示不是。
AVRational sample_aspect_ratio; // 视频帧的样本宽高比
int64_t pts; // 以time_base为单位的呈现时间戳(应向用户显示帧的时间)。
int64_t pkt_dts; // 从AVPacket复制而来的dts时间,当没有pts时间是,pkt_dts可以替代pts。
int coded_picture_number; // 按解码先后排序的,解码图像数
int display_picture_number; // 按显示前后排序的,显示图像数。
int quality; // 帧质量,从1~FF_LAMBDA_MAX之间取之,1表示最好,FF_LAMBDA_MAX之间取之表示最坏。
void *opaque; // user的私有数据。
int interlaced_frame; // 图片的内容是隔行扫描的(交错帧)。
int top_field_first; // 如果内容是隔行扫描的,则首先显示顶部字段。
int sample_rate; // 音频数据的采样率
uint64_t channel_layout; // 音频数据的通道布局。
/**
* AVBuffer引用,当前帧数据。 如果所有的元素为NULL,则此帧不是引用计数。 必须连续填充此数组,
* 即如果buf [i]为非NULL,j <i,buf[j]也必须为非NULL。
*
* 每个数据平面最多可以有一个AVBuffer,因此对于视频,此数组始终包含所有引用。
* 对于具有多于AV_NUM_DATA_POINTERS个通道的平面音频,可能有多个缓冲区可以容纳在此阵列中。
* 然后额外的AVBufferRef指针存储在extended_buf数组中。
*/
AVBufferRef *buf[AV_NUM_DATA_POINTERS];
AVBufferRef **extended_buf; // AVBufferRef的指针
int nb_extended_buf; // extended_buf的数量
enum AVColorSpace colorspace; // YUV颜色空间类型。
int64_t best_effort_timestamp; // 算法预测的timestamp
int64_t pkt_pos; // 记录上一个AVPacket输入解码器的位置。
int64_t pkt_duration; // packet的duration
AVDictionary *metadata;
int channels; // 音频的通道数。
int pkt_size; // 包含压缩帧的相应数据包的大小。
} AVFrame;
1.4 URL
指一个拉流地址 --- 音频流和视频流的地址
给你一个 IP,你可以从这个 IP 上获取持续的信息
URL(Uniform Resource Locator)是指统一资源定位符
1.5 程序的编译执行
问题来源
1、LVGL 工程
2、程序问题
3、视频问题
二、LVGL 列表
1、列表和目录操作结合
就可以做一个播放的菜单
glob 函数
glob 函数获取一个路径下所有的.mp4 文件
将文件名作为 list 列表的按钮文本填上
2、点击不同的列表按钮,获取要播放的视频文件名
执行写的程序去播放
3、列表隐藏
列表一直显示比较遮挡视线
将大小改成 0,0;如果 0,0 不行,就改成 1,1
代码
video
#include <stdio.h>
#include "lvgl.h"
#include "custom.h"
#include "libavformat/avformat.h"
#include "libavutil/imgutils.h"
#include "libswscale/swscale.h"
#include <pthread.h>
#include <unistd.h>
void *pthread_video_func(void *argv);
void custom_init(lv_ui *ui)
{
/* Add your codes here */
pthread_t pd = 0;
pthread_create(&pd, NULL, pthread_video_func, NULL);
pthread_detach(pd);
}
uint8_t outdata[4 * 1024 * 600]; //4字节,1024*600像素点个数
lv_img_dsc_t _my_ChMkJlbKzGCIAglRAAT73pUukQsAALI1gK_rlQABPv2931_alpha_1024x600 = {
.header.cf = LV_IMG_CF_TRUE_COLOR_ALPHA,
.header.always_zero = 0,
.header.reserved = 0,
.header.w = 1024,
.header.h = 600,
.data_size = 614400 * LV_IMG_PX_SIZE_ALPHA_BYTE,
.data = outdata,
};
//LVGL是一个持续运行的进程
//如果阻塞LVGL的主进程 --- 会导致程序直接结束
void *pthread_video_func(void *argv)
{
// 1.获取核心上下文指针
AVFormatContext * avfmt = avformat_alloc_context();
// 2.打开输入流文件
avformat_open_input(&avfmt, "/home/lwl/GUI-Guider-Projects/mp4file/cf.mp4", NULL, NULL);
// 3.获取输入流
avformat_find_stream_info(avfmt, NULL); //到此会获取
// avfmt->streams[0] --- 视频流
// avfmt->streams[1] --- 音频流
// 4.获取编码器
AVCodecContext * avcodectx = avfmt->streams[0]->codec;
AVCodec *avcodec = avcodec_find_decoder(avcodectx->codec_id);
// 5.初始化解码器
avcodec_open2(avcodectx, avcodec, NULL);
// 申请输出显示空间 --- 得到的数据需要一块空间存放
//申请空间大小 --- 和图像的格式,以及格式分辨率有关
//enum AVPixelFormat
int size = av_image_get_buffer_size(AV_PIX_FMT_BGRA, 1024, 600, 1);
uint8_t *data = av_malloc(size); //后续得到空间首地址,最终使用的是data这个空间,但是视频格式转换后只能放在输出流指针中
// 申请输出流指针
AVFrame * avfrm = av_frame_alloc(); //这个是最终的frm
// 绑定输出流和输出显示空间
av_image_fill_arrays(avfrm->data, avfrm->linesize, data, AV_PIX_FMT_BGRA, 1024, 600, 1);
// 申请格式转换上下文
struct SwsContext * swsctx = sws_getContext(avfmt->streams[0]->codecpar->width, avfmt->streams[0]->codecpar->height, avcodectx->pix_fmt, 1024, 600, AV_PIX_FMT_BGRA, SWS_GAUSS, NULL, NULL, NULL);
// 10.申请输入流指针
AVPacket * avpkt = av_packet_alloc(); //存放输入流中一帧图像
AVFrame * tempfrm = av_frame_alloc();
// 11.读取一帧数据
while(1)
{
if(!run_flag)
{
sleep(1); //主动释放cpu资源
continue;
}
if(av_read_frame(avfmt, avpkt) != 0)
{
break;
}
if(avpkt->stream_index == 1)
{//音频数据不做处理,如果不处理就会很卡
continue;
}
// 发送给解码器
avcodec_send_packet(avcodectx, avpkt);
// 从解码器读取一帧数据
avcodec_receive_frame(avcodectx, tempfrm); //解码后的数据还没有进行格式转换 --- 需要定义一个中间变量
// 格式转换
sws_scale(swsctx, tempfrm->data, tempfrm->linesize, 0, tempfrm->height, avfrm->data, avfrm->linesize);
//此时此刻,在data空间中就存放了得到的最终显示数据
memcpy(outdata, data, sizeof(outdata));
// 显示在屏幕上
lv_img_set_src(self_ui->screen_img_1, &_my_ChMkJlbKzGCIAglRAAT73pUukQsAALI1gK_rlQABPv2931_alpha_1024x600);
usleep(33000);
}
}
list 记得添加事件
#include <stdio.h>
#include "lvgl.h"
#include "custom.h"
#include <glob.h>
#include "libavformat/avformat.h"
#include "libavutil/imgutils.h"
#include "libswscale/swscale.h"
#include <pthread.h>
#include <unistd.h>
void my_add_list_btn(lv_ui *ui, char *name);
void custom_init(lv_ui *ui)
{
/* Add your codes here */
glob_t fn = {0};//显示歌单
glob("/home/lwl/GUI-Guider-Projects/mp4file/*.mp4", 0, NULL, &fn);//lwl/GUI-Guider-Projects/mp4file/
for(int i = 0; i < fn.gl_pathc; i++)
{
printf("%d.%s\n", i + 1, fn.gl_pathv[i] + 38);//glob函数打印歌单38
my_add_list_btn(ui, fn.gl_pathv[i] + 38);
}
globfree(&fn);
}
void my_add_list_btn(lv_ui *ui, char *name)
{
lv_obj_t *btn = lv_list_add_btn(ui->screen_list_1, LV_SYMBOL_BATTERY_3, name);
lv_style_set_pad_top(&style_screen_list_1_extra_btns_main_default, 5);
lv_style_set_pad_left(&style_screen_list_1_extra_btns_main_default, 5);
lv_style_set_pad_right(&style_screen_list_1_extra_btns_main_default, 5);
lv_style_set_pad_bottom(&style_screen_list_1_extra_btns_main_default, 5);
lv_style_set_border_width(&style_screen_list_1_extra_btns_main_default, 0);
lv_style_set_text_color(&style_screen_list_1_extra_btns_main_default, lv_color_hex(0x0D3055));
lv_style_set_text_font(&style_screen_list_1_extra_btns_main_default, &lv_font_montserratMedium_32);
lv_style_set_text_opa(&style_screen_list_1_extra_btns_main_default, 255);
lv_style_set_radius(&style_screen_list_1_extra_btns_main_default, 3);
lv_style_set_bg_opa(&style_screen_list_1_extra_btns_main_default, 0);
lv_obj_add_style(btn, &style_screen_list_1_extra_btns_main_default, LV_PART_MAIN | LV_STATE_DEFAULT);
// 为按钮添加点击事件
lv_obj_add_event_cb(btn, screen_list_1_item0_event_handler, LV_EVENT_CLICKED, ui);
}