Ubuntu 16.04.5下FFmpeg编译与开发环境搭建
PC环境: Ubuntu 18.04 上面只要安装下面的提示安装即可,基本上不必再下载依赖库的源代码进行编译和安装
编译步骤:
1, 安装相关工具:
- sudo apt install -y autoconf automake build-essential git libass-dev libfreetype6-dev libsdl2-dev libtheora-dev libtool libva-dev libvdpau-dev libvorbis-dev libxcb1-dev libxcb-shm0-dev libxcb-xfixes0-dev pkg-config texinfo wget zlib1g-dev
- sudo apt install -y nasm yasm cmake mercurial
2,git clone git://source.ffmpeg.org/ffmpeg.git ffmpeg,得到文件夹ffmpeg,进入ffmpeg文件夹:
- ./configure --enable-shared --prefix=/usr/local/ffmpeg
安装到/usr/local/ffmpeg下,可通过“--prefix=安装目录”进行修改。--enable-shared:指定生成动态库,默认是静态库。静态库不方便后续开发。
2.sudo make
3.sudo make install
3,添加ffmpeg库的链接:
在/etc/ld.so.conf中 末尾添加 /usr/local/ffmpeg/lib 即可,执行
- sudo ldconfig
4.安装VS Code
参考https://blog.csdn.net/u011258217/article/details/78693564
--------------------------------------------------- ubuntu 16.04.5 下还需要按照一下提示进行安装依赖 -------------------------------------
sudo apt-get update sudo apt-get -y --force-yes install autoconf automake build-essential libass-dev libfreetype6-dev \ libsdl1.2-dev libtheora-dev libtool libva-dev libvdpau-dev libvorbis-dev libxcb1-dev libxcb-shm0-dev \ libxcb-xfixes0-dev pkg-config texi2html zlib1g-dev
mkdir ~/ffmpeg_sources
sudo apt install yasm libx264 libx265 libfdk-aac libmp3lame libopus libvpx -y
sudo apt install yasm libx264-dev libx265-dev libfdk-aac-dev libmp3lame-dev libopus-dev libvpx-dev -y
cd ~/ffmpeg_sources
wget http://www.tortall.net/projects/yasm/releases/yasm-1.3.0.tar.gz
tar xzvf yasm-1.3.0.tar.gz
cd yasm-1.3.0
./configure --prefix="$HOME/ffmpeg_build" --bindir="$HOME/bin"
make
make install
make distclean
cd ~/ffmpeg_sources
wget http://download.videolan.org/pub/x264/snapshots/last_x264.tar.bz2
tar xjvf last_x264.tar.bz2
cd x264-snapshot*
PATH="$HOME/bin:$PATH" ./configure --prefix="$HOME/ffmpeg_build" --bindir="$HOME/bin" --enable-static
PATH="$HOME/bin:$PATH" make
make install
make distclean
问题:不知道是不是环境变量的关系,很多标准command不能用,参考另一个笔记:linux下提示bash:command not found
export PATH=/bin:/sbin:/usr/bin:/usr/sbin
5、libx265
265的源一直没下载下来,也暂时不用x265,所以没有安装,后续编译ffmpeg不编译x265就可以。
6、libfdk-aac
cd ~/ffmpeg_sources
wget -O fdk-aac.tar.gz https://github.com/mstorsjo/fdk-aac/tarball/master
git clone https://github.com/mstorsjo/fdk-aac.git
https://github.com/mstorsjo/fdk-aac/archive/master.zip
tar xzvf fdk-aac.tar.gz
cd mstorsjo-fdk-aac*
autoreconf -fiv
./configure --prefix="$HOME/ffmpeg_build" --disable-shared
make
make install
make distclean
sudo apt-get install nasm
cd ~/ffmpeg_sources
wget http://downloads.sourceforge.net/project/lame/lame/3.99/lame-3.99.5.tar.gz
tar xzvf lame-3.99.5.tar.gz
cd lame-3.99.5
./configure --prefix="$HOME/ffmpeg_build" --enable-nasm --disable-shared
make
make install
make distclean
cd ~/ffmpeg_sources
wget http://downloads.xiph.org/releases/opus/opus-1.1.tar.gz
tar xzvf opus-1.1.tar.gz
cd opus-1.1
./configure --prefix="$HOME/ffmpeg_build" --disable-shared
make
make install
make distclean
cd ~/ffmpeg_sources
wget http://webm.googlecode.com/files/libvpx-v1.3.0.tar.bz2 ## !!!!! 这个应该下不下来,可以百度一下替代, 换上最新版本的,不要用这个,有点太低了。
http://ftp.osuosl.org/pub/blfs/conglomeration/libvpx/
wget -c http://ftp.osuosl.org/pub/blfs/conglomeration/libvpx/libvpx-1.7.0.tar.gz
tar xjvf libvpx-v1.3.0.tar.bz2 cd libvpx-v1.3.0 PATH="$HOME/bin:$PATH" ./configure --prefix="$HOME/ffmpeg_build" --disable-examples --disable-unit-tests PATH="$HOME/bin:$PATH" make make install make clean
## 下面的这个不必了,直接上github上下载源码即可, https://github.com/FFmpeg/FFmpeg.git
安装编译ffmpeg
cd ~/ffmpeg_sources wget http://ffmpeg.org/releases/ffmpeg-snapshot.tar.bz2 tar xjvf ffmpeg-snapshot.tar.bz2 cd ffmpeg PATH="$HOME/bin:$PATH" PKG_CONFIG_PATH="$HOME/ffmpeg_build/lib/pkgconfig" ./configure \ --prefix="$HOME/ffmpeg_build" \ --pkg-config-flags="--static" \ --extra-cflags="-I$HOME/ffmpeg_build/include" \ --extra-ldflags="-L$HOME/ffmpeg_build/lib" \ --bindir="$HOME/bin" \ --enable-gpl \ --enable-libass \ --enable-libfdk-aac \ --enable-libfreetype \ --enable-libmp3lame \ --enable-libopus \ --enable-libtheora \ --enable-libvorbis \ --enable-libvpx \ --enable-libx264 \ --enable-libx265 \ --enable-nonfree PATH="$HOME/bin:$PATH" make make install make distclean hash -r
查看ffmpeg是否安装成功,用ffmpeg -version
之后我们就可以使用ffmpeg 将文件转化成ts文件,以备m3u8-segmenter切片,搭建hls流媒体服务器。
另外有参考源代码:
https://github.com/lspbeyond/p264decoder.git
思科家的: https://github.com/cisco/openh264.git
--------------------------------------------------------------------------- 分割线 -------------------------------------------------------------------------
5.测试环境
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 | #include <stdio.h> #include <libavformat/avformat.h> #include <libswscale/swscale.h> #include <libavfilter/avfilter.h> #define dbmsgc(fmt, args ...) printf("cong:%s[%d]: "fmt"\n", __FUNCTION__, __LINE__,##args) //#define dbmsg(fmt, args ...) printf("cong:%s:%s[%d]: "fmt"\n",__FILE__, __FUNCTION__, __LINE__,##args) int main( int argc, char **argv) { int i=0; AVFormatContext *pFormatCtx = NULL; avcodec_register_all(); #if CONFIG_AVDEVICE avdevice_register_all(); #endif avfilter_register_all(); av_register_all(); if (avformat_open_input(&pFormatCtx, argv[1], NULL, NULL)!=0) return -1; // Couldn't open file if (avformat_find_stream_info(pFormatCtx, NULL)<0) return -1; // Couldn't find stream inform av_dump_format(pFormatCtx,0, 0, 0); return 0; } |
6..编写Makefile
1 2 3 4 5 6 7 8 9 10 11 | FFMPEG= /usr/local/ffmpeg CC= gcc CFLAGS=-g -I$(FFMPEG) /include LDFLAGS = -L$(FFMPEG) /lib/ -lswscale -lswresample -lavformat -lavdevice -lavcodec -lavutil -lavfilter -lm TARGETS= test all: $(TARGETS) test : test .c $(CC) $(CFLAGS) -o $@ $^ $(LDFLAGS) -std=c++11 #注意这里的-std=c++11 clean: rm -rf $(TARGETS) |
7.make
8../test
测试代码2:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 | /* * Copyright (c) 2015 Ludmila Glinskih * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ /** * H264 codec test. */ #include "libavutil/adler32.h" #include "libavcodec/avcodec.h" #include "libavformat/avformat.h" #include "libavutil/imgutils.h" static int video_decode_example( const char *input_filename) { AVCodec *codec = NULL; AVCodecContext *ctx= NULL; AVCodecParameters *origin_par = NULL; AVFrame *fr = NULL; uint8_t *byte_buffer = NULL; AVPacket pkt; AVFormatContext *fmt_ctx = NULL; int number_of_written_bytes; int video_stream; int got_frame = 0; int byte_buffer_size; int i = 0; int result; int end_of_stream = 0; result = avformat_open_input(&fmt_ctx, input_filename, NULL, NULL); if (result < 0) { av_log(NULL, AV_LOG_ERROR, "Can't open file\n" ); return result; } result = avformat_find_stream_info(fmt_ctx, NULL); if (result < 0) { av_log(NULL, AV_LOG_ERROR, "Can't get stream info\n" ); return result; } video_stream = av_find_best_stream(fmt_ctx, AVMEDIA_TYPE_VIDEO, -1, -1, NULL, 0); if (video_stream < 0) { av_log(NULL, AV_LOG_ERROR, "Can't find video stream in input file\n" ); return -1; } origin_par = fmt_ctx->streams[video_stream]->codecpar; codec = avcodec_find_decoder(origin_par->codec_id); if (!codec) { av_log(NULL, AV_LOG_ERROR, "Can't find decoder\n" ); return -1; } ctx = avcodec_alloc_context3(codec); if (!ctx) { av_log(NULL, AV_LOG_ERROR, "Can't allocate decoder context\n" ); return AVERROR(ENOMEM); } result = avcodec_parameters_to_context(ctx, origin_par); if (result) { av_log(NULL, AV_LOG_ERROR, "Can't copy decoder context\n" ); return result; } result = avcodec_open2(ctx, codec, NULL); if (result < 0) { av_log(ctx, AV_LOG_ERROR, "Can't open decoder\n" ); return result; } fr = av_frame_alloc(); if (!fr) { av_log(NULL, AV_LOG_ERROR, "Can't allocate frame\n" ); return AVERROR(ENOMEM); } byte_buffer_size = av_image_get_buffer_size(ctx->pix_fmt, ctx->width, ctx->height, 16); byte_buffer = av_malloc(byte_buffer_size); if (!byte_buffer) { av_log(NULL, AV_LOG_ERROR, "Can't allocate buffer\n" ); return AVERROR(ENOMEM); } printf ( "#tb %d: %d/%d\n" , video_stream, fmt_ctx->streams[video_stream]->time_base.num, fmt_ctx->streams[video_stream]->time_base.den); i = 0; av_init_packet(&pkt); do { if (!end_of_stream) if (av_read_frame(fmt_ctx, &pkt) < 0) end_of_stream = 1; if (end_of_stream) { pkt.data = NULL; pkt.size = 0; } if (pkt.stream_index == video_stream || end_of_stream) { got_frame = 0; if (pkt.pts == AV_NOPTS_VALUE) pkt.pts = pkt.dts = i; result = avcodec_decode_video2(ctx, fr, &got_frame, &pkt); if (result < 0) { av_log(NULL, AV_LOG_ERROR, "Error decoding frame\n" ); return result; } if (got_frame) { number_of_written_bytes = av_image_copy_to_buffer(byte_buffer, byte_buffer_size, ( const uint8_t* const *)fr->data, ( const int *) fr->linesize, ctx->pix_fmt, ctx->width, ctx->height, 1); if (number_of_written_bytes < 0) { av_log(NULL, AV_LOG_ERROR, "Can't copy image to buffer\n" ); return number_of_written_bytes; } printf ( "%d, %10" PRId64 ", %10" PRId64 ", %8" PRId64 ", %8d, 0x%08lx\n" , video_stream, fr->pts, fr->pkt_dts, fr->pkt_duration, number_of_written_bytes, av_adler32_update(0, ( const uint8_t*)byte_buffer, number_of_written_bytes)); } av_packet_unref(&pkt); av_init_packet(&pkt); } i++; } while (!end_of_stream || got_frame); av_packet_unref(&pkt); av_frame_free(&fr); avcodec_close(ctx); avformat_close_input(&fmt_ctx); avcodec_free_context(&ctx); av_freep(&byte_buffer); return 0; } int main( int argc, char **argv) { if (argc < 2) { av_log(NULL, AV_LOG_ERROR, "Incorrect input\n" ); return 1; } if (video_decode_example(argv[1]) != 0) return 1; return 0; } |
测试代码3:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 | /* * Copyright (c) 2015 Ludmila Glinskih * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ /** * draw_horiz_band test. */ #include "libavutil/adler32.h" #include "libavcodec/avcodec.h" #include "libavformat/avformat.h" #include "libavutil/imgutils.h" uint8_t *slice_byte_buffer; uint8_t slice_byte_buffer_size; int draw_horiz_band_called; static void draw_horiz_band(AVCodecContext *ctx, const AVFrame *fr, int offset[4], int slice_position, int type, int height) { int i; const AVPixFmtDescriptor *pix_fmt_desc; int chroma_w, chroma_h; int shift_slice_position; int shift_height; draw_horiz_band_called = 1; pix_fmt_desc = av_pix_fmt_desc_get(ctx->pix_fmt); chroma_w = -((-ctx->width) >> pix_fmt_desc->log2_chroma_w); chroma_h = -((-height) >> pix_fmt_desc->log2_chroma_h); shift_slice_position = -((-slice_position) >> pix_fmt_desc->log2_chroma_h); shift_height = -((-ctx->height) >> pix_fmt_desc->log2_chroma_h); for (i = 0; i < height; i++) { memcpy (slice_byte_buffer + ctx->width * slice_position + i * ctx->width, fr->data[0] + offset[0] + i * fr->linesize[0], ctx->width); } for (i = 0; i < chroma_h; i++) { memcpy (slice_byte_buffer + ctx->width * ctx->height + chroma_w * shift_slice_position + i * chroma_w, fr->data[1] + offset[1] + i * fr->linesize[1], chroma_w); } for (i = 0; i < chroma_h; i++) { memcpy (slice_byte_buffer + ctx->width * ctx->height + chroma_w * shift_height + chroma_w * shift_slice_position + i * chroma_w, fr->data[2] + offset[2] + i * fr->linesize[2], chroma_w); } } static int video_decode( const char *input_filename) { AVCodec *codec = NULL; AVCodecContext *ctx= NULL; AVCodecParameters *origin_par = NULL; uint8_t *byte_buffer = NULL; AVFrame *fr = NULL; AVPacket pkt; AVFormatContext *fmt_ctx = NULL; int number_of_written_bytes; int video_stream; int got_frame = 0; int byte_buffer_size; int result; int end_of_stream = 0; draw_horiz_band_called = 0; result = avformat_open_input(&fmt_ctx, input_filename, NULL, NULL); if (result < 0) { av_log(NULL, AV_LOG_ERROR, "Can't open file\n" ); return result; } result = avformat_find_stream_info(fmt_ctx, NULL); if (result < 0) { av_log(NULL, AV_LOG_ERROR, "Can't get stream info\n" ); return result; } video_stream = av_find_best_stream(fmt_ctx, AVMEDIA_TYPE_VIDEO, -1, -1, NULL, 0); if (video_stream < 0) { av_log(NULL, AV_LOG_ERROR, "Can't find video stream in input file\n" ); return -1; } origin_par = fmt_ctx->streams[video_stream]->codecpar; codec = avcodec_find_decoder(origin_par->codec_id); if (!codec) { av_log(NULL, AV_LOG_ERROR, "Can't find decoder\n" ); return -1; } ctx = avcodec_alloc_context3(codec); if (!ctx) { av_log(NULL, AV_LOG_ERROR, "Can't allocate decoder context\n" ); return AVERROR(ENOMEM); } result = avcodec_parameters_to_context(ctx, origin_par); if (result) { av_log(NULL, AV_LOG_ERROR, "Can't copy decoder context\n" ); return result; } ctx->draw_horiz_band = draw_horiz_band; ctx->thread_count = 1; result = avcodec_open2(ctx, codec, NULL); if (result < 0) { av_log(ctx, AV_LOG_ERROR, "Can't open decoder\n" ); return result; } fr = av_frame_alloc(); if (!fr) { av_log(NULL, AV_LOG_ERROR, "Can't allocate frame\n" ); return AVERROR(ENOMEM); } if ( strcmp (codec->name, "flv" ) && strcmp (codec->name, "mpeg4" ) && strcmp (codec->name, "huffyuv" )) { av_log(NULL, AV_LOG_ERROR, "Wrong codec\n" ); return -1; } byte_buffer_size = av_image_get_buffer_size(ctx->pix_fmt, ctx->width, ctx->height, 32); byte_buffer = av_malloc(byte_buffer_size); if (!byte_buffer) { av_log(NULL, AV_LOG_ERROR, "Can't allocate buffer\n" ); return AVERROR(ENOMEM); } slice_byte_buffer = av_malloc(byte_buffer_size); if (!slice_byte_buffer) { av_log(NULL, AV_LOG_ERROR, "Can't allocate buffer\n" ); return AVERROR(ENOMEM); } memset (slice_byte_buffer, 0, byte_buffer_size); slice_byte_buffer_size = byte_buffer_size; av_init_packet(&pkt); do { if (!end_of_stream) { if (av_read_frame(fmt_ctx, &pkt) < 0) { end_of_stream = 1; } } if (end_of_stream) { pkt.data = NULL; pkt.size = 0; } if (pkt.stream_index == video_stream || end_of_stream) { got_frame = 0; result = avcodec_decode_video2(ctx, fr, &got_frame, &pkt); if (result < 0) { av_log(NULL, AV_LOG_ERROR, "Error decoding frame\n" ); return result; } if (got_frame) { number_of_written_bytes = av_image_copy_to_buffer(byte_buffer, byte_buffer_size, ( const uint8_t* const *)fr->data, ( const int *) fr->linesize, ctx->pix_fmt, ctx->width, ctx->height, 1); if (number_of_written_bytes < 0) { av_log(NULL, AV_LOG_ERROR, "Can't copy image to buffer\n" ); return number_of_written_bytes; } if (draw_horiz_band_called == 0) { av_log(NULL, AV_LOG_ERROR, "draw_horiz_band haven't been called!\n" ); return -1; } if (av_adler32_update(0, ( const uint8_t*)byte_buffer, number_of_written_bytes) != av_adler32_update(0, ( const uint8_t*)slice_byte_buffer, number_of_written_bytes)) { av_log(NULL, AV_LOG_ERROR, "Decoded frames with and without draw_horiz_band are not the same!\n" ); return -1; } } av_packet_unref(&pkt); av_init_packet(&pkt); } } while (!end_of_stream || got_frame); av_packet_unref(&pkt); av_frame_free(&fr); avcodec_close(ctx); avformat_close_input(&fmt_ctx); avcodec_free_context(&ctx); av_freep(&byte_buffer); av_freep(&slice_byte_buffer); return 0; } int main( int argc, char **argv) { if (argc < 2) { av_log(NULL, AV_LOG_ERROR, "Incorrect input: expected %s <name of a video file>\nNote that test works only for huffyuv, flv and mpeg4 decoders\n" , argv[0]); return 1; } if (video_decode(argv[1]) != 0) return 1; return 0; } |
本文参考了:
https://blog.csdn.net/wawayu_0/article/details/80564349
https://www.cnblogs.com/candycaicai/p/4689459.html
特此致谢。
每一个不曾起舞的日子,都是对生命的辜负。
But it is the same with man as with the tree. The more he seeks to rise into the height and light, the more vigorously do his roots struggle earthward, downward, into the dark, the deep - into evil.
其实人跟树是一样的,越是向往高处的阳光,它的根就越要伸向黑暗的地底。----尼采
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· go语言实现终端里的倒计时
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现
· 25岁的心里话