• 音视频开发第一课-使用C语言开发视频播放器 650元IT外包开发全程记录


    1. 界面设计

    目标效果:

     

    1. 创建MFC对话框项目,或者直接使用项目模板

    主要选择64位平台。

    1. 拖放控件

    1. 设置播放器区域的背景

    把资源bg.bmp拷贝到项目目录的res目录下。

    把bg.bmp添加当项目的资源中

    把图片控件的类型修改为Bitmap, 并设置为刚添加的图片资源。

    重新调整布局。

    1. 添加播放视频的Frame类型的图片控件

    大小和位置与bg.bmp重合。

    ID修改为: IDC_STATIC_VIDEO

    1. 播放视频

    配置VLC开发环境,(项目模板中已经配置好了!)

    手动配置VLC开发环境详解

    1. 下载VLC安装包

     

    1. 解压
    2. 把相关的资源直接拷贝到项目目录下

    在项目目录下,创建目录VLC

    把VLC安装的解压目录下的include目录和lib目录,拷贝到项目目录下的VLC目录下

     

     

    1. 配置环境

    头文件目录:

    VLC\include

    库文件目录:

    VLC\lib

    附加依赖项:

    libvlccore.lib

    libvlc.lib

    把动态库(安装包的解压目录下)拷贝到项目的可执行文件目录下

    axvlc.dll

    libvlc.dll

    libvlccore.dll

    npvlc.dll

    把plugins目录拷贝到可执行文件目录下

    创建视频播放器模块

    创建 VideoPlayer.h和 VideoPlayer.cpp

    VideoPlayer.h

    1. #include "pch.h"
    2. #ifdef _WIN32
    3. #include
    4. typedef SSIZE_T ssize_t;
    5. #endif
    6. #include "vlc/vlc.h"

    因为vlc在windows平台需要 ssize_t类型,否则VLC的接口会编译失败!

    定义播放器的数据类型

    VideoPlayer.h

    1. typedef struct VideoPlayer {
    2. libvlc_media_player_t* player;
    3. libvlc_instance_t* instance;
    4. HWND hwnd; //播放窗口
    5. }video_player_t;

    添加播放器的相关接口

    VideoPlayer.h

    1. bool videoPlayerInit(); // 视频播放器的初始化
    2. bool videoPlayerPlay(char* filePath); // 播放指定视频
    3. bool videoPlayerPause(); // 暂停
    4. void videoPlayerStop(); // 停止

    VideoPlayer.cpp

    1. #include "pch.h"
    2. #include "VideoPlayer.h"
    3. bool videoPlayerInit(video_player_t* player) {
    4. if (!player) return false;
    5. player->instance = libvlc_new(0, nullptr);
    6. return player->instance != NULL;
    7. }
    8. bool videoPlayerPlay(video_player_t* player, char* filePath)
    9. {
    10. bool bRet = false;
    11. if (!player || !filePath || strlen(filePath) == 0)
    12. return false;
    13. if (player->instance == NULL) {
    14. videoPlayerInit(player);
    15. return false;
    16. }
    17. libvlc_media_t* pVlcMedia = libvlc_media_new_path(player->instance, filePath);
    18. if (pVlcMedia == NULL) {
    19. return false;
    20. }
    21. player->player = libvlc_media_player_new_from_media(pVlcMedia);
    22. if (player->player) {
    23. // 设置播放窗口
    24. libvlc_media_player_set_hwnd(player->player, player->hwnd);
    25. // 播放
    26. if (libvlc_media_player_play(player->player) != -1)
    27. bRet = true;
    28. libvlc_media_release(pVlcMedia);
    29. }
    30. return bRet;
    31. }
    32. bool videoPlayerPause(video_player_t* player) {
    33. if (!player || !player->player) {
    34. return false;
    35. }
    36. if (libvlc_media_player_can_pause(player->player)) {
    37. libvlc_media_player_pause(player->player);
    38. return true;
    39. }
    40. return false;
    41. }
    42. void videoPlayerStop(video_player_t* player) {
    43. libvlc_media_player_stop(player->player);
    44. libvlc_media_player_release(player->player);
    45. player->player = nullptr;
    46. }

    测试播放器

    MyPlayerDlg.cpp

    1. #include "VideoPlayer.h"
    2. VideoPlayer mvPlayer; //MV播放
    3. void CPlayerTmpDlg::OnBnClickedButton1()
    4. {
    5. // 测试,以下三行代码,测试后删除
    6. videoPlayerInit(&mvPlayer);
    7. mvPlayer.hwnd = GetDlgItem(IDC_STATIC_VIDEO)->GetSafeHwnd();
    8. videoPlayerPlay(&mvPlayer, "E:\\FFOutput\\9.mp4");
    9. }

    歌曲播放列表

    歌曲列表的界面设计

    1. 添加list control控件
    2. 修改list control控件的id为: IDC_LIST_MUSIC

     

    1. 为这个控件添加成员变量m_music_list;
    2. 把风格设置为“报表风格”(Report)

     

    1. 设置为可以单行选择

     

    1. 添加单行选择事件

     选择NM_CLICK

     

    单击确定后,自动生成事件处理函数:OnNMClickListMusic

    添加静态歌曲数据

    1. 创建列表的表头

    在对话框的初始化函数中,添加:

    1. // TODO: 在此添加额外的初始化代码
    2. m_music_list.SetTextColor(RGB(0, 50, 255));
    3. m_music_list.InsertColumn(0, _T("歌曲"));
    4. m_music_list.InsertColumn(1, _T("歌手"));
    5. m_music_list.SetColumnWidth(0, 108);
    6. m_music_list.SetColumnWidth(1, 68);
    7. // LVS_EX_GRIDLINES 显示项及其子项周围的网格线
    8. // LVS_EX_FULLROWSELECT 选择整行, 否则只显示这一行的一个单元格
    9. m_music_list.SetExtendedStyle(LVS_EX_GRIDLINES | LVS_EX_FULLROWSELECT);

    创建数据层模块

    1. 定义数据库中的实体(music)对应的数据类型

    Database.h

    1. // 定义歌曲类型
    2. typedef struct music
    3. {
    4. int music_id;
    5. char music_name[128];
    6. char music_path[256];
    7. char mv_path[256];
    8. char music_class[16];
    9. char music_singer[64];
    10. }music_t;
    1. 定义接口,向数据库获取,获取多行数据

    Database.h

    int get_all_music_info(music_t* musics, int max_count); //可以进一步优化为按“页”来获取

    Database.cpp

    1. int get_all_music_info(music_t* musics, int max_count)
    2. {
    3. music_t data[] = {
    4. 1, "Cry again", "E:\\FFOutput\\CHERRSEE-Cry again.mp3", "E:\\FFOutput\\CHERRSEE-Cry again.mp4", "流行音乐", "CHERRSEE",
    5. 2, "烽烟四起", "E:\\FFOutput\\fengyanshiqi.mp3", "E:\\FFOutput\\fengyanshiqi.mp4", "流行音乐", "胡彦斌",
    6. 3, "大风吹", "E:\\FFOutput\\dafengchui.mp3", "E:\\FFOutput\\dafengchui.mp4", "流行音乐", "王赫野",
    7. 4, "人间惊鸿客", "E:\\FFOutput\\ren jian jing hong ke.mp3", "E:\\FFOutput\\ren jian jing hong ke.mp4", "中国风", "叶里",
    8. 5, "谪仙", "E:\\FFOutput\\dixian.mp3", "E:\\FFOutput\\dixian.mp4", "中国风", "叶里",
    9. };
    10. int count = sizeof(data) / sizeof(data[0]);
    11. for (int i = 0; i < count; i++) {
    12. musics[i].music_id = data[i].music_id;
    13. strcpy(musics[i].music_name, data[i].music_name);
    14. strcpy(musics[i].music_path, data[i].music_path);
    15. strcpy(musics[i].mv_path, data[i].mv_path);
    16. strcpy(musics[i].music_class, data[i].music_class);
    17. strcpy(musics[i].music_singer, data[i].music_singer);
    18. }
    19. return count;
    20. }

    从数据模块获取数据

    在对话框的.cpp文件中添加全局变量musics

    1. #define MAX_NUM 100
    2. music_t musics[MAX_NUM] = { 0 }; //保存所有歌曲

    在对话框的.cpp文件中添加头文件 Database.h

    #include "Database.h"

    在对话框的初始化函数中,添加:

    1. //7.获取数据库中全部歌曲
    2. int count = get_all_music_info(musics, MAX_NUM);
    3. for (int i = 0; i
    4. m_music_list.InsertItem(i, musics[i].music_name);
    5. m_music_list.SetItemText(i, 1, musics[i].music_singer);
    6. }

    调试效果:

    调整歌曲列表字体大小

    在对话框的初始化函数中,添加:

    1. // 歌曲列表字体
    2. LOGFONT logfont; //定义一个“逻辑单位字体”
    3. CFont* pfont1 = m_music_list.GetFont();
    4. pfont1->GetLogFont(&logfont); // 获取逻辑单位字体
    5. logfont.lfHeight *= 1.5; //这里可以修改字体的高比例
    6. logfont.lfWidth *= 1.5; //这里可以修改字体的宽比例
    7. static CFont font1;
    8. font1.CreateFontIndirect(&logfont); //使用逻辑单位字体,来创建一个字体
    9. m_music_list.SetFont(&font1);
    10. font1.Detach(); // 解绑

    测试效果:

     

    1. 通过歌曲列表实现选择播放
    1. 初始化播放器

    在对话框窗口的初始化函数中,添加:

    1. // 初始化播放器
    2. videoPlayerInit(&mvPlayer);
    3. mvPlayer.hwnd = GetDlgItem(IDC_STATIC_VIDEO)->GetSafeHwnd();

    添加全局变量,表示已经选择的歌曲,和当前正在播放的歌曲路径

    1. int selectedRow; // 选择了第几行
    2. CString currentMVPath = "";
    1. 在列表的NM_CLICK事件处理程序中,更新selectedRow
    1. void CMyPlayerDlg::OnNMClickListMusic(NMHDR* pNMHDR, LRESULT* pResult)
    2. {
    3. LPNMITEMACTIVATE pNMItemActivate = reinterpret_cast(pNMHDR);
    4. // pNMListView 表示单击了哪个个单元格
    5. NMLISTVIEW* pNMListView = (NMLISTVIEW*)pNMHDR;
    6. if (pNMListView->iItem != -1) { //如果单击了空白行,该成员等于-1
    7. selectedRow = pNMListView->iItem;
    8. }
    9. *pResult = 0;
    10. }
    1. 在播放按钮的单击事件处理函数中,实现播放控制

    PLAY 和 PAUSE  两种按钮文字,交替出现。

    对于ALC视频库,如果MV已经暂停了,再次使用暂停操作后,又会播放!

    1. void CMyPlayerDlg::OnBnClickedButtonPlayMv()
    2. {
    3. if (selectedRow < 0) return;
    4. CString status;
    5. GetDlgItemText(IDC_BUTTON_PLAY_MV, status);
    6. if (status == "PLAY") { // 准备播放
    7. if (currentMVPath == "") { //准备第一次播放
    8. videoPlayerPlay(&mvPlayer, musics[selectedRow].mv_path);
    9. }
    10. else { // 播放器播放过MV了
    11. if (currentMVPath == musics[selectedRow].mv_path) {
    12. videoPlayerPause(&mvPlayer);
    13. } else { // 在播放列表中选择了其它歌曲
    14. videoPlayerStop(&mvPlayer); // 停止原来的歌曲
    15. videoPlayerPlay(&mvPlayer, musics[selectedRow].mv_path);
    16. }
    17. }
    18. SetDlgItemText(IDC_BUTTON_PLAY_MV, "PAUSE");
    19. currentMVPath = musics[selectedRow].mv_path;
    20. } else { // 标题为"PAUSE", 正在播放,准备暂停
    21. videoPlayerPause(&mvPlayer);
    22. SetDlgItemText(IDC_BUTTON_PLAY_MV, "PLAY");
    23. }
    24. }
    1. 测试效果

    检查连续单击按钮的效果,以及选择了其它歌曲后的效果。

    1. 添加进度条控制
    1. 添加滑块控件 Slider Control

    ID修改为IDC_SLIDER_MV

    为这个控件,添加变量成员 m_slider_mv;

    1. 设置刻度和范围
    1. #define SLIDER_MV_MAX 1000
    2. BOOL CMyPlayerDlg::OnInitDialog()
    3. {
    4. ......
    5. m_slider_mv.SetRange(0, SLIDER_MV_MAX);
    6. return TRUE; // 除非将焦点设置到控件,否则返回 TRUE
    7. }
    1. 为滑块添加NM_RELEASEDCAPTURE事件

    就是当拖住滑块,然后松开滑块时触发该事件。

     

    1. void CMyPlayerDlg::OnNMReleasedcaptureSliderMv(NMHDR* pNMHDR, LRESULT* pResult)
    2. {
    3. float pos = m_slider_mv.GetPos();
    4. float percent = pos / SLIDER_MV_MAX;
    5. libvlc_media_player_set_position(mvPlayer.m_pVlcPlayer, percent);
    6. *pResult = 0;
    7. }

    测试效果:拖动滑块,验证能否快进或者后退。

    1. 添加定时器,更新滑块的播放位置

    在播放MV的时候,启动定时器

    1. void CMyPlayerDlg::OnBnClickedButtonPlayMv()
    2. {
    3. if (selectedRow < 0) return;
    4. CString status;
    5. GetDlgItemText(IDC_BUTTON_PLAY_MV, status);
    6. if (status == "PLAY") { // 准备播放
    7. if (currentMVPath == "") { //准备第一次播放
    8. videoPlayerPlay(&mvPlayer, musics[selectedRow].mv_path);
    9. // 创建定时器,定时更新播放进度条
    10. SetTimer(1, //定时器的编号, 可以定义为宏
    11. 50, //定时器时间间隔,单位ms
    12. NULL);
    13. }
    14. else { // 播放器播放过MV了
    15. if (currentMVPath == musics[selectedRow].mv_path) {
    16. videoPlayerPause(&mvPlayer);
    17. KillTimer(1);
    18. } else { // 在播放列表中选择了其它歌曲
    19. videoPlayerStop(&mvPlayer); // 停止原来的歌曲
    20. videoPlayerPlay(&mvPlayer, musics[selectedRow].mv_path);
    21. SetTimer(1, 50, NULL);
    22. }
    23. }
    24. SetDlgItemText(IDC_BUTTON_PLAY_MV, "PAUSE");
    25. currentMVPath = musics[selectedRow].mv_path;
    26. } else { // 标题为"PAUSE", 正在播放,准备暂停
    27. KillTimer(1);
    28. videoPlayerPause(&mvPlayer);
    29. SetDlgItemText(IDC_BUTTON_PLAY_MV, "PLAY");
    30. }
    31. }

    添加定时事件

    为播放器对话框添加定时消息。

     

    1. void CMyPlayerDlg::OnTimer(UINT_PTR nIDEvent)
    2. {
    3. // TODO: 在此添加消息处理程序代码和/或调用默认值
    4. if (nIDEvent == 1) {
    5. // libvlc_media_player_get_position返回已经播放的进度(0-1)
    6. int scale = libvlc_media_player_get_position(mvPlayer.m_pVlcPlayer) * SLIDER_MV_MAX;
    7. m_slider_mv.SetPos(scale);
    8. }
    9. CDialogEx::OnTimer(nIDEvent);
    10. }

    测试:验证滑块能否自动修改进度

    发现BUG: 此时就不能手动拖动滑块了,还没有拖动完,滑块就被定时器修改位置了。

    解决方案:拖动滑块时,关闭定时器,释放滑块时,再启动定时器。

    为播放器对话框,添加HSCROLL消息:

     

    1. void CMyPlayerDlg::OnHScroll(UINT nSBCode, UINT nPos, CScrollBar* pScrollBar)
    2. {
    3. // TODO: 在此添加消息处理程序代码和/或调用默认值
    4. if (&m_slider_mv == (CSliderCtrl*)pScrollBar) {
    5. KillTimer(1);
    6. }
    7. CDialogEx::OnHScroll(nSBCode, nPos, pScrollBar);
    8. }
    1. void CMyPlayerDlg::OnNMReleasedcaptureSliderMv(NMHDR* pNMHDR, LRESULT* pResult)
    2. {
    3. float pos = m_slider_mv.GetPos();
    4. float percent = pos / SLIDER_MV_MAX;
    5. libvlc_media_player_set_position(mvPlayer.m_pVlcPlayer, percent);
    6. SetTimer(1, 50, NULL);
    7. *pResult = 0;
    8. }
    1. 歌曲播放结束后,再播放按钮的标题修改为PLAY
    1. void CMyPlayerDlg::OnTimer(UINT_PTR nIDEvent)
    2. {
    3. // TODO: 在此添加消息处理程序代码和/或调用默认值
    4. if (nIDEvent == 1) {
    5. // libvlc_media_player_get_position返回已经播放的进度(0-1)
    6. int scale = libvlc_media_player_get_position(mvPlayer.m_pVlcPlayer) * SLIDER_MV_MAX;
    7. m_slider_mv.SetPos(scale);
    8. if (libvlc_media_player_get_state(mvPlayer.m_pVlcPlayer) == libvlc_Ended) {
    9. SetDlgItemText(IDC_BUTTON_PLAY_MV, "PLAY");
    10. currentMVPath = "";
    11. KillTimer(1);
    12. videoPlayerStop(&mvPlayer);
    13. }
    14. }
    15. CDialogEx::OnTimer(nIDEvent);
    16. }

    用数据库存储歌曲信息

    安装mysql数据库

    1. 下载mysql数据库安装包

    1. 安装mysql

    把安装包保存到D:/mysql目录下(手动创建该目录)

    下载后解压

    解压后的目录如下:

    1. 配置mysql的环境变量

    把mysql的安装目录 :

    D:\mysql\mysql-8.0.24-winx64\mysql-8.0.24-winx64\bin

    添加到path环境变量中:

     

    1. 生成mysql的data文件

    在cmd窗口执行命令:

    mysqld --initialize-insecure --user=mysql

    该命令可能会比较耗时(几分钟)

     

    1. 安装mysql

    执行命令:

    mysqld -install

     

    1. 启动mysql服务

    执行命令:

    net start MySQL

     

    1. 登录Mysql

    执行命令:

    mysql -u root -p

    直接回车即可(刚安装完后,mysql 的root用户的密码是空)

     

    1. 修改mysql的root用户的密码

    在mysql> 后面分别输入以下两条命令:

    //打开mysql数据库

    use mysql  

    //把root用户的密码修改为123456

    update mysql.user set authentication_string=("123456") where user="root";  

    1. 退出mysql

    quit

     

    1. 重新登录

    mysql  -uroot  -p

    如果提示登录失败:

     

    则重新以空密码的方式登录mysql, 然后再使用另一条命令修改密码:

    mysql  -uroot  -p

    ALTER USER 'root'@'localhost' IDENTIFIED WITH mysql_native_password BY '123456';

    退出myql, 再次使用新密码登录即可

    编写数据库脚本

    setup.sql (注意文件格式要使用ANSI编码,不要使用utf-8编码)

    1. drop database if exists `music_player`;
    2. create database music_player;
    3. use music_player;
    4. create table music_store(
    5. music_id int primary key,
    6. music_name varchar(128) not null,
    7. music_path varchar(256) not null,
    8. music_mv_path varchar(256) not null,
    9. muisc_class enum('流行音乐','纯音乐','中国风') default ('流行音乐'),
    10. music_singer varchar(64) not null
    11. );
    12. insert into music_store values(1,'Cry again','E:\\FFOutput\\CHERRSEE-Cry again.mp3','E:\\FFOutput\\CHERRSEE-Cry again.mp4','流行音乐','CHERRSEE');
    13. insert into music_store values(2,'烽烟四起','E:\\FFOutput\\fengyanshiqi.mp3','E:\\FFOutput\\fengyanshiqi.mp4','流行音乐','胡彦斌');
    14. insert into music_store values(3,'大风吹','E:\\FFOutput\\dafengchui.mp3','E:\\FFOutput\\dafengchui.mp4','流行音乐','王赫野');
    15. insert into music_store values(4,'人间惊鸿客','E:\\FFOutput\\ren jian jing hong ke.mp3','E:\\FFOutput\\ren jian jing hong ke.mp4','中国风','叶里');
    16. insert into music_store values(5,'谪仙','E:\\FFOutput\\dixian.mp3','E:\\FFOutput\\dixian.mp4','中国风','叶里');
    17. select * from music_store;

    执行数据库脚本

    mysql -uroot -p < E:\0-直播课\13-650的IT外包-视频播放器\setup.sql

    查看数据,检查脚本执行效果:

     

    配置mysql的开发环境

    需要配置好mysql的开发环境,才能访问mysql数据库

    3.1)头文件目录

    D:\mysql\mysql-8.0.24-winx64\mysql-8.0.24-winx64\include

    3.2)库文件目录

    D:\mysql\mysql-8.0.24-winx64\mysql-8.0.24-winx64\lib

    3.3)配置附加依赖项

    libmysql.lib

    3.4)配置动态库

    libmysql.dll

    把libmysql.dll拷贝到项目的可执行文件所在的目录

    该动态库在“D:\mysql\mysql-8.0.24-winx64\mysql-8.0.24-winx64\lib”目录下。

    使用数据库来获取歌曲信息

    修改数据模块.

    定义mysql数据库的基本信息

    Database.h

    1. #define DB_HOST "127.0.0.1"
    2. #define DB_USER "root"
    3. #define DB_USER_PASSWD "123456"
    4. #define DB_NAME "music_player"
    5. #define DB_PROT 3306

    数据库的默认端口是3306

    添加连接数据库的接口

    Database.h

    1. #include
    2. bool connect_mysql(MYSQL *mysql);

    Database.cpp

    1. #include"pch.h" //预编译头文件,比如CString类型需要使用
    2. #include "database.h" //注意:pch.h必须放在 mysql.h 之前!
    3. bool connect_mysql(MYSQL *mysql) {
    4. mysql_init(mysql);
    5. // 设置数据库的字符集为"gbk"
    6. mysql_options(mysql, MYSQL_SET_CHARSET_NAME, "gbk");
    7. if (!mysql_real_connect(mysql, DB_HOST, DB_USER, DB_USER_PASSWD, DB_NAME, DB_PROT, 0, 0)) {
    8. CString error_info;
    9. error_info.Format("数据库连接出错,错误原因: %s", mysql_error(mysql));
    10. AfxMessageBox(error_info);
    11. return false;
    12. }
    13. return true;
    14. }

    修改获取数据的接口

    把之前的get_all_music_info的实现全部删除。

    Database.cpp

    1. int get_all_music_info(music_t* musics, int max_count)//查询数据库中所有内容
    2. {
    3. MYSQL mysql;
    4. MYSQL_RES* res;
    5. MYSQL_ROW row;
    6. // 连接数据库
    7. if (!connect_mysql(&mysql)) {
    8. return 0;
    9. }
    10. // select * from music_store limit 0,3 #返回前3行
    11. char sql[256];
    12. sprintf_s(sql, sizeof(sql), " select * from music_store limit 0,%d", max_count);
    13. if (mysql_query(&mysql, sql)) { // 发送数据库查询指令
    14. CString error_info;
    15. error_info.Format("数据库查询出错,错误原因: %s", mysql_error(&mysql));
    16. AfxMessageBox(error_info);
    17. mysql_close(&mysql);
    18. return 0;
    19. }
    20. res = mysql_store_result(&mysql); // 执行查询
    21. int count = 0;
    22. for (int i = 0; i < max_count; i++) {
    23. row = mysql_fetch_row(res); // 返回一行查询结果
    24. if (row == NULL) {
    25. break;
    26. }
    27. musics[i].music_id = atoi(row[0]);
    28. strcpy(musics[i].music_name, row[1]);
    29. strcpy(musics[i].music_path, row[2]);
    30. strcpy(musics[i].mv_path, row[3]);
    31. strcpy(musics[i].music_class, row[4]);
    32. strcpy(musics[i].music_singer, row[5]);
    33. count++;
    34. }
    35. mysql_free_result(res);
    36. mysql_close(&mysql);
    37. return count;
    38. }

    项目提升

    1. 联网,实现视频流播放
    2. 弹幕,很多桌面播放器没有弹幕功能(弹幕需要服务器共享信息)
    3. 添加音频播放和管理,类似“酷狗音乐”
    4. 项目借鉴:Qt版的酷狗

    今天的分享就到这里了,大家要好好学C语言/C++哟~

    欢迎转行和学习编程的伙伴,利用更多的资料学习成长比自己琢磨更快哦!

    对于准备学习C/C++编程的小伙伴,如果你想更好的提升你的编程核心能力(内功)不妨从现在开始!

    整理分享(多年学习的源码、项目实战视频、项目笔记,基础入门教程)加君羊获取哦~
    C语言C++编程学习交流圈子,QQ群:763855696
     

  • 相关阅读:
    Linux服务器上Neo4j的安装、迁移
    GANs综述
    LSTM+CRF模型
    c# SerialPort HEX there is no data received
    2022年高薪测试必备核心技术
    免费开源线上社交交友婚恋系统平台 可打包小程序 支持二开 源码交付!
    【数据结构与算法】之深入解析“分割数组的最多方案数”的求解思路与算法示例
    (二)初识Vue
    Springboot: ApplicationRunner、CommandLineRunner的应用场景、区别及使用示例
    定时任务 Quartz
  • 原文地址:https://blog.csdn.net/weixin_55751709/article/details/126310765