基于FFmpeg,实现播放器功能

在这里插入图片描述

一、客户端选择音视频文件

MainActivity

package com.anniljing.ffmpegnative;import android.Manifest;
import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
import android.database.Cursor;
import android.net.Uri;
import android.os.Bundle;
import android.provider.MediaStore;
import android.provider.OpenableColumns;
import android.text.TextUtils;
import android.util.Log;
import android.view.SurfaceHolder;import com.anniljing.ffmpegnative.databinding.ActivityMainBinding;import androidx.activity.result.ActivityResultLauncher;
import androidx.activity.result.contract.ActivityResultContracts;
import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;
import pub.devrel.easypermissions.EasyPermissions;public class MainActivity extends AppCompatActivity implements SurfaceHolder.Callback {private static final String TAG = MainActivity.class.getSimpleName();private ActivityMainBinding binding;private Context mContext;private FFmpegPlayer mFFmpegPlayer;private String[] PERMISSIONS_STORAGE = {Manifest.permission.READ_EXTERNAL_STORAGE, Manifest.permission.WRITE_EXTERNAL_STORAGE};private ActivityResultLauncher<Intent> mResultLauncher;private String videoPath;private SurfaceHolder mSurfaceHolder;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);mContext = this;binding = ActivityMainBinding.inflate(getLayoutInflater());setContentView(binding.getRoot());if (!EasyPermissions.hasPermissions(mContext, PERMISSIONS_STORAGE)) {EasyPermissions.requestPermissions(MainActivity.this, "", 100, PERMISSIONS_STORAGE);}mSurfaceHolder = binding.surfaceView.getHolder();mSurfaceHolder.addCallback(this);mFFmpegPlayer = new FFmpegPlayer();mResultLauncher = registerForActivityResult(new ActivityResultContracts.StartActivityForResult(), result -> {if (result.getResultCode() == RESULT_OK) {Uri data = result.getData().getData();Log.e(TAG, "onActivityResult:" + data);videoPath = getPathFromContentUri(mContext, data);Log.e(TAG, "getPathFromContentUri:" + videoPath);}});binding.play.setOnClickListener((view) -> {Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT);intent.addCategory(Intent.CATEGORY_OPENABLE);intent.setType("video/*");mResultLauncher.launch(intent);});}@Overridepublic void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {super.onRequestPermissionsResult(requestCode, permissions, grantResults);EasyPermissions.onRequestPermissionsResult(requestCode, permissions, grantResults, this);}public static String getPathFromContentUri(Context context, Uri uri) {if (uri == null) return null;String filePath = null;if (uri.getScheme().equals(ContentResolver.SCHEME_CONTENT)) {ContentResolver contentResolver = context.getContentResolver();Cursor cursor = contentResolver.query(uri, null, null, null, null);if (cursor != null && cursor.moveToFirst()) {int columnIndex = cursor.getColumnIndex(OpenableColumns.DISPLAY_NAME);if (columnIndex != -1) {String displayName = cursor.getString(columnIndex);if (displayName != null) {filePath = getFilePathFromDisplayName(context, uri, displayName);}}cursor.close();}}return filePath;}private static String getFilePathFromDisplayName(Context context, Uri uri, String displayName) {String filePath = null;ContentResolver contentResolver = context.getContentResolver();Uri mediaUri = MediaStore.Video.Media.EXTERNAL_CONTENT_URI;Cursor cursor = contentResolver.query(mediaUri, null, MediaStore.Video.Media.DISPLAY_NAME + "=?", new String[]{displayName}, null);if (cursor != null && cursor.moveToFirst()) {int columnIndex = cursor.getColumnIndex(MediaStore.Video.Media.DATA);if (columnIndex != -1) {filePath = cursor.getString(columnIndex);}cursor.close();}return filePath;}@Overrideprotected void onResume() {super.onResume();}@Overridepublic void surfaceCreated(@NonNull SurfaceHolder holder) {if (!TextUtils.isEmpty(videoPath)) {new Thread(() -> {if (mFFmpegPlayer != null) {mFFmpegPlayer.native_start(videoPath, mSurfaceHolder.getSurface());}}).start();}}@Overridepublic void surfaceChanged(@NonNull SurfaceHolder holder, int format, int width, int height) {}@Overridepublic void surfaceDestroyed(@NonNull SurfaceHolder holder) {}
}

1.1、访问视频文件目录

   Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT);intent.addCategory(Intent.CATEGORY_OPENABLE);intent.setType("video/*");mResultLauncher.launch(intent);

1.2、解析返回的视频路径

ActivityResultLauncher 是 Android Jetpack 中的一个组件,用于简化处理启动活动并接收结果的过程。它是在 Android API 级别 30(Android 11)引入的新特性,旨在替代过时的 startActivityForResult 方法。

ActivityResultLauncher 使用了一种更简单和类型安全的方式来处理活动结果。它通过注册一个回调并在回调中处理结果,而不需要重写 onActivityResult 方法。这使得代码更加清晰和易于维护。

private ActivityResultLauncher<Intent> mResultLauncher;mResultLauncher = registerForActivityResult(new ActivityResultContracts.StartActivityForResult(), new ActivityResultCallback<ActivityResult>() {@Overridepublic void onActivityResult(ActivityResult result) {if (result.getResultCode() == RESULT_OK) {Uri data = result.getData().getData();Log.e(TAG, "onActivityResult:" + data);videoPath = getPathFromContentUri(mContext, data);Log.e(TAG, "getPathFromContentUri:" + videoPath);}}});
public static String getPathFromContentUri(Context context, Uri uri) {if (uri == null) return null;String filePath = null;if (uri.getScheme().equals(ContentResolver.SCHEME_CONTENT)) {ContentResolver contentResolver = context.getContentResolver();Cursor cursor = contentResolver.query(uri, null, null, null, null);if (cursor != null && cursor.moveToFirst()) {int columnIndex = cursor.getColumnIndex(OpenableColumns.DISPLAY_NAME);if (columnIndex != -1) {String displayName = cursor.getString(columnIndex);if (displayName != null) {filePath = getFilePathFromDisplayName(context, uri, displayName);}}cursor.close();}}return filePath;}private static String getFilePathFromDisplayName(Context context, Uri uri, String displayName) {String filePath = null;ContentResolver contentResolver = context.getContentResolver();Uri mediaUri = MediaStore.Video.Media.EXTERNAL_CONTENT_URI;Cursor cursor = contentResolver.query(mediaUri, null, MediaStore.Video.Media.DISPLAY_NAME + "=?", new String[]{displayName}, null);if (cursor != null && cursor.moveToFirst()) {int columnIndex = cursor.getColumnIndex(MediaStore.Video.Media.DATA);if (columnIndex != -1) {filePath = cursor.getString(columnIndex);}cursor.close();}return filePath;}

二、声明native方法

FFmpegPlayer.java

package com.anniljing.ffmpegnative;import android.view.Surface;public class FFmpegPlayer {static {System.loadLibrary("ffmpegnative");}public native void native_start(String path, Surface surface);
}

三、jni层实现

native-lib.cpp

#include <jni.h>
#include <string>
#include <android/native_window_jni.h>
#include <unistd.h>extern "C" {
#include <libavformat/avformat.h>
#include <libavcodec/avcodec.h>
#include <libswscale/swscale.h>
#include "libavutil/imgutils.h"
#include "AndroidLog.h"JNIEXPORT void JNICALL
Java_com_anniljing_ffmpegnative_FFmpegPlayer_native_1start(JNIEnv *env, jobject thiz, jstring path,jobject surface) {const char *mPath = env->GetStringUTFChars(path, 0);//初始化AVFormatContextAVFormatContext *avFormatContext = avformat_alloc_context();AVDictionary *pDictionary = NULL;av_dict_set(&pDictionary, "timeout", "3000000", 0);// 打开输入文件int ret = avformat_open_input(&avFormatContext, mPath, NULL, &pDictionary);if (ret) {return;}// 获取流信息avformat_find_stream_info(avFormatContext, NULL);// 查找视频流int video_stream_index = -1;for (int i = 0; i < (avFormatContext->nb_streams); i++) {if (avFormatContext->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) {video_stream_index = i;break;}}LOGD("video stream index:%d", video_stream_index);AVStream* videoStream = avFormatContext->streams[video_stream_index];AVRational timeBase = videoStream->time_base;// 计算帧率double frameRate = av_q2d(timeBase);LOGD("video rate:%f",frameRate);// 获取视频流解码器参数AVCodecParameters *codecParameters = avFormatContext->streams[video_stream_index]->codecpar;// 查找视频流解码器AVCodec *avCodecVideo = avcodec_find_decoder(codecParameters->codec_id);// 创建解码器上下文AVCodecContext *avCodecContext = avcodec_alloc_context3(avCodecVideo);// 设置解码器参数avcodec_parameters_to_context(avCodecContext, codecParameters);// 打开解码器avcodec_open2(avCodecContext, avCodecVideo, NULL);// 创建图像转换上下文SwsContext *swsContext = sws_getContext(avCodecContext->width, avCodecContext->height,avCodecContext->pix_fmt, avCodecContext->width,avCodecContext->height, AV_PIX_FMT_RGBA, SWS_BILINEAR,0, 0, 0);// 获取 ANativeWindow 对象ANativeWindow *nativeWindow = ANativeWindow_fromSurface(env, surface);// 设置渲染格式和大小ANativeWindow_setBuffersGeometry(nativeWindow, avCodecContext->width, avCodecContext->height,WINDOW_FORMAT_RGBA_8888);// 分配渲染缓冲区ANativeWindow_Buffer outBuffer;//从视频流读取数据包到avpacketAVPacket *avPacketVideo = av_packet_alloc();//从音视频文件中读取下一帧while (av_read_frame(avFormatContext, avPacketVideo) >= 0) {// 将要解码的数据包送入解码器avcodec_send_packet(avCodecContext, avPacketVideo);AVFrame *avFrameVideo = av_frame_alloc();//从解码器内部缓存中提取解码后的音视频帧ret = avcodec_receive_frame(avCodecContext, avFrameVideo);if (ret == AVERROR(EAGAIN)) {continue;} else if (ret < 0) {break;}uint8_t *dst_data[4];int dst_linesize[4];av_image_alloc(dst_data, dst_linesize, avCodecContext->width, avCodecContext->height,AV_PIX_FMT_RGBA, 1);// 锁定 Surface 并获取渲染缓冲区ANativeWindow_lock(nativeWindow, &outBuffer, NULL);// 将解码后的帧转换为目标格式sws_scale(swsContext, avFrameVideo->data, avFrameVideo->linesize, 0, avFrameVideo->height,dst_data, dst_linesize);//渲染uint8_t *first = static_cast<uint8_t *>(outBuffer.bits);uint8_t *src_data = dst_data[0];int dstStride = outBuffer.stride * 4;int src_linesize = dst_linesize[0];for (int i = 0; i < outBuffer.height; ++i) {memcpy(first + i * dstStride, src_data + i * src_linesize, dstStride);}// 解锁 SurfaceANativeWindow_unlockAndPost(nativeWindow);sleep(frameRate);av_frame_free(&avFrameVideo);}LOGD("release");av_packet_unref(avPacketVideo);ANativeWindow_release(nativeWindow);sws_freeContext(swsContext);avcodec_free_context(&avCodecContext);avformat_close_input(&avFormatContext);env->ReleaseStringUTFChars(path, mPath);
}}

3.1、AVFormatContext

AVFormatContext 是 FFmpeg 库中的一个结构体,用于表示音视频封装格式的上下文信息。它包含了音视频流的封装格式、容器级别的参数和状态,以及与输入输出相关的信息。

typedef struct AVFormatContext {/*** A class for logging and @ref avoptions. Set by avformat_alloc_context().* Exports (de)muxer private options if they exist.*/const AVClass *av_class;/*** 输入容器格式.* 用于分流,通过avformat_open_input()设置.*/struct AVInputFormat *iformat;/*** 输出容器格式。** 用于混流,必须在avformat_write_header()调用前设置.*/struct AVOutputFormat *oformat;/*** Format private data. This is an AVOptions-enabled struct* if and only if iformat/oformat.priv_class is not NULL.** - muxing: set by avformat_write_header()* - demuxing: set by avformat_open_input()*/void *priv_data;/*** I/O 上下文.** - 分流: 在avformat_open_input() 之前设置(用户必须手动释放)或者通过avformat_open_input()*		  自动设置.* - 混流: 在avformat_write_header()之前设置.用户必须注意及时关闭/释放IO上下文。** 不要设置AVFMT_NOFILE标志给iformat/oformat.flags。因为这种情况下,该值为NULL,混/分流器会通* 过其它方式处理I/O。*/AVIOContext *pb;/* 后面都是流信息 *//*** 信号流属性标志,AVFMTCTX_*的组合.* 通过libavformat设置.*/int ctx_flags;/*** AVFormatContext.streams中的元素数量,其实就是流的总数.** 通过avformat_new_stream()设置, 禁止其它代码修改。*/unsigned int nb_streams;/*** 媒体中,所有流的列表,新的流由avformat_new_stream()创建。** - 分流: 流在avformat_open_input()函数中由libavformat创建。如果AVFMTCTX_NOHEANDER被设置*        带ctx_flags中,新的流可能出现在av_read_frame()中。* - 混流: 流在avformat_write_header()函数之前被用户创建** 在avformat_free_context()函数中,通过libavformat释放。*/AVStream **streams;/*** 输入输出文件名** - 分流: 通过avformat_open_input()设置。* - 混流: 在avformat_write_header()调用前,可以被使用者设置。*/char filename[1024];/*** 组件第一帧的位置,用AV_TIME_BASE分数秒表示。禁止直接设置,由AVStream的值推导而来。** - 分流:通过libavformat设置.*/int64_t start_time;/*** 留的时长,以AV_TIME_BASE分数秒为单位。如果您不知道任何单个流的持续时间,也不设置其中的任何一* 个,请仅设置此值。 如果没有设置,该值可以被AVStream推导出来** 只用于分流操作,通过libavformat设置。*/int64_t duration;/*** 总流的比特率以bit/s为单位,如果流不可用,该值为0。如果流文件大小和时长已知,不要直接设置它,* FFmpeg会自动计算。*/int64_t bit_rate;unsigned int packet_size;int max_delay;/*** 修改分/混流器操作的标志,一个AVFMT_FLAG_*的组合。* 在avformat_open_input() / avformat_write_header()调用之前用户自行设置.*/int flags;
#define AVFMT_FLAG_GENPTS       0x0001 ///< Generate missing pts even if it requires parsing future frames.
#define AVFMT_FLAG_IGNIDX       0x0002 ///< Ignore index.
#define AVFMT_FLAG_NONBLOCK     0x0004 ///< Do not block when reading packets from input.
#define AVFMT_FLAG_IGNDTS       0x0008 ///< Ignore DTS on frames that contain both DTS & PTS
#define AVFMT_FLAG_NOFILLIN     0x0010 ///< Do not infer any values from other values, just return what is stored in the container
#define AVFMT_FLAG_NOPARSE      0x0020 ///< Do not use AVParsers, you also must set AVFMT_FLAG_NOFILLIN as the fillin code works on frames and no parsing -> no frames. Also seeking to frames can not work if parsing to find frame boundaries has been disabled
#define AVFMT_FLAG_NOBUFFER     0x0040 ///< Do not buffer frames when possible
#define AVFMT_FLAG_CUSTOM_IO    0x0080 ///< The caller has supplied a custom AVIOContext, don't avio_close() it.
#define AVFMT_FLAG_DISCARD_CORRUPT  0x0100 ///< Discard frames marked corrupted
#define AVFMT_FLAG_FLUSH_PACKETS    0x0200 ///< Flush the AVIOContext every packet.
/*** 混流时,尽量避免将随机/不可控的数据写入输出中,包括随机IDs,实时时间戳/日期,混流器版本等等。** 该标记主要用于测试*/
#define AVFMT_FLAG_BITEXACT         0x0400
#define AVFMT_FLAG_MP4A_LATM    0x8000 ///< Enable RTP MP4A-LATM payload
#define AVFMT_FLAG_SORT_DTS    0x10000 ///< try to interleave outputted packets by dts (using this flag can slow demuxing down)
#define AVFMT_FLAG_PRIV_OPT    0x20000 ///< Enable use of private options by delaying codec open (this could be made default once all code is converted)
#if FF_API_LAVF_KEEPSIDE_FLAG
#define AVFMT_FLAG_KEEP_SIDE_DATA 0x40000 ///< Don't merge side data but keep it separate. Deprecated, will be the default.
#endif
#define AVFMT_FLAG_FAST_SEEK   0x80000 ///< Enable fast, but inaccurate seeks for some formats
#define AVFMT_FLAG_SHORTEST   0x100000 ///< Stop muxing when the shortest stream stops.
#define AVFMT_FLAG_AUTO_BSF   0x200000 ///< Wait for packet data before writing a header, and add bitstream filters as requested by the muxer/*** 从指定容器格式的输入中读取最大数据的大小。* 仅用于分流操作,用户可以在avformat_open_input()函数前设置。*/int64_t probesize;/*** 从指定容器格式的输入中读取的最大数据时长(以AV_TIME_BASE为单位)。* 仅用于分流操作,在avformat_find_stream_info()调用前设置。为0时,让avformat自动选择。*/int64_t max_analyze_duration;const uint8_t *key;int keylen;unsigned int nb_programs;AVProgram **programs;/*** 强制视频codec_id.* 分流操作: 用户设置。*/enum AVCodecID video_codec_id;/*** 强制音频codec_id.* 分流操作: 用户设置。*/enum AVCodecID audio_codec_id;/*** 强制字幕codec_id.* 分流操作: 用户设置。*/enum AVCodecID subtitle_codec_id;/*** 每个索引使用的内存最大值(以字节为单位)。* 如果索引超出内存限制,项目会被丢弃以保持较小的内存占用。这回导致seeking较慢和不准确(取决于分流* 器)* 完全内存索引是强制性的分解器将忽略这一点。* - 混流操作: 不实用* - 分流操作: 由用户设置*/unsigned int max_index_size;/*** 从设备获取的实时帧缓冲的最大内存大小(以字节为单位)*/unsigned int max_picture_buffer;/*** AVChapter数组中的章节数量。* 混流时,章节信息通畅会写在文件头中,所以nb_chapters应该在写文件头之前被初始化。一些混流器(例如* mov、mkv)可以将章节写在预告中。为了在预告中撰写章节,在write_header调用时nb_chapters必须为* 并且在write_trailer被调用时为非0数。* - 混流操作: 用户设置* - 分流操作: libavformat设置*/unsigned int nb_chapters;AVChapter **chapters;/*** 适用于整个文件的元数据。** - 分流操作: libavformat在avformat_open_input()函数中设置。* - 混流操作: 调用者可以在avformat_write_header()函数调用前设置。** 通过libavformat在函数avformat_free_context()中释放。*/AVDictionary *metadata;/*** 从Unix纪元(1970年1月1日00:00)开始,以真实世界时间开始流的开始时间,以微秒为单位。 即,流在现* 实世界被使用的pts=0时间。* - 混流操作: 在avformat_write_header()调用前被调用者设置。如果设置为0或AV_NOPTS_VALUE,则* 将使用当前时间(wall-time)。* - 分流操作: 由libavformat设置. 如果AV_NOPTS_VALUE未知,注意,一定数量的帧被获取后,该值可能* 变得已知。*/int64_t start_time_realtime;/*** 用于确定avformat_find_stream_info()中帧率的帧数。* 仅用于分流,在avformat_find_stream_info()调用前由调用者设置*/int fps_probe_size;/*** 错误识别; 较高的值将检测到更多的错误,但可能会错误检测一些或多或少有效的部分作为错误。 在* avformat_open_input()之前由调用方设置的仅用于解分流。*/int error_recognition;/*** I/O层自定义中断回调函数。** 分流操作: avformat_open_input()调用前由用户设置.* 混流操作: avformat_write_header()调用前由用户设置(主要用于AVFMT_NOFILE 格式)。如果用它来* 打开文件,该回调也会传递给avio_open2().*/AVIOInterruptCB interrupt_callback;/*** 启用debug标志。*/int debug;
#define FF_FDEBUG_TS        0x0001/*** Maximum buffering duration for interleaving.** To ensure all the streams are interleaved correctly,* av_interleaved_write_frame() will wait until it has at least one packet* for each stream before actually writing any packets to the output file.* When some streams are "sparse" (i.e. there are large gaps between* successive packets), this can result in excessive buffering.** This field specifies the maximum difference between the timestamps of the* first and the last packet in the muxing queue, above which libavformat* will output a packet regardless of whether it has queued a packet for all* the streams.** Muxing only, set by the caller before avformat_write_header().*/int64_t max_interleave_delta;/*** 允许非标准和实验性扩展* @see AVCodecContext.strict_std_compliance*/int strict_std_compliance;/*** 供用户检测文件上发生事件的标志。事件处理后,用户必须清除标志。AVFMT_EVENT_FLAG_ *的组合。*/int event_flags;
#define AVFMT_EVENT_FLAG_METADATA_UPDATED 0x0001 ///< The call resulted in updated metadata./*** 在等待第一个时间戳时要读取的最大数据包数。仅用于解码。*/int max_ts_probe;/*** 避免混流过程中的负面时间戳。 AVFMT_AVOID_NEG_TS_ *常量中的任何值。 请注意,这只适用于使用* av_interleaved_write_frame。 (interleave_packet_per_dts正在使用中)* - 混流: 用户设置* - 分流: 不使用*/int avoid_negative_ts;
#define AVFMT_AVOID_NEG_TS_AUTO             -1 ///< Enabled when required by target format
#define AVFMT_AVOID_NEG_TS_MAKE_NON_NEGATIVE 1 ///< Shift timestamps so they are non negative
#define AVFMT_AVOID_NEG_TS_MAKE_ZERO         2 ///< Shift timestamps so that they start at 0/*** 传输流id。* 这将被移入分流器的私有选项。 因此没有API / ABI兼容性*/int ts_id;/*** 音频预加载以微秒为单位。 请注意,并非所有格式都支持此功能,如果在不支持的情况下使用它,则可能会发* 生不可预知的情况。* - 编码: 用户设置* - 解码: 不使用*/int audio_preload;/*** 最大块时间(以微秒为单位)。 请注意,并非所有格式都支持此功能,如果在不支持的情况下使用它,则可能* 会发生不可预知的情况。* - 编码: 用户设置* - 解码: 不使用*/int max_chunk_duration;/*** 最大块大小(以字节为单位)。注意,并非所有格式都支持此功能,如果在不支持的情况下使用它,可能会发* 生不可预知的情况。* - 编码: 用户设置* - 解码: 不使用*/int max_chunk_size;/***  强制使用wallclock时间戳作为pts / dts数据包在B帧存在的情况下存在未定义的结果。* - 编码: 不使用* - 解码: 用户设置*/int use_wallclock_as_timestamps;/*** avio标志,用于强制使用AVIO_FLAG_DIRECT。* - 编码: 不使用* - 解码: 用户设置*/int avio_flags;/*** 时长字段可以通过各种方式进行计算,并且可以使用此字段了解时长是如何计算的。* - 编码: 不使用* - 解码: 用户读取*/enum AVDurationEstimationMethod duration_estimation_method;/*** 打开流时跳过初始字节* - 编码: 不使用* - 解码: 用户设置*/int64_t skip_initial_bytes;/*** 正确的单个时间戳溢出* - 编码: 不使用* - 解码: 用户设置*/unsigned int correct_ts_overflow;/*** 强制seeking到任意帧(即使没有关键帧)* - 编码: 不使用* - 解码: 用户设置*/int seek2any;/*** 在每个数据包之后刷新I / O上下文。* - 编码: 用户设置* - 解码: 不使用*/int flush_packets;/*** 格式探测分数。 最高分是AVPROBE_SCORE_MAX,当分流器探测格式时设置它。* - 编码: 不使用* - 解码: avformat设置,用户读取*/int probe_score;/*** 要最大限度地读取以识别格式的字节数。* - 编码: 不使用* - 解码: 用户设置*/int format_probesize;/*** ',' 分割的支持的解码器刘表,如果值为NULL,表示支持所有解码器。* - 编码: 不使用* - 解码: 用户设置*/char *codec_whitelist;/*** ',' 分割的支持的分流器列表,如果值为NULL,所有分流器都支持。* - 编码: 不使用* - 解码: 用户设置*/char *format_whitelist;/*** libavformat内部使用的不透明字段。 不得以任何方式访问。*/AVFormatInternal *internal;/*** IO重定位标志。* 当基础IO上下文读指针重新定位时,例如在执行基于字节的查找时,这由avformat设置。 分流器可以使用* 标志来检测这种变化。*/int io_repositioned;/*** 强制视频解码器。强制数据解码器。这允许使用强制指定的解码器,即使有多个相同的codec_id.* 分流: 用户设置*/AVCodec *video_codec;/*** 强制音频解码器。强制数据解码器。这允许使用强制指定的解码器,即使有多个相同的codec_id.* 分流: 用户设置*/AVCodec *audio_codec;/*** 强制数据解码器。这允许使用强制指定的解码器,即使有多个相同的codec_id.* 分流: 用户设置*/AVCodec *subtitle_codec;/*** 强制数据解码器。这允许使用强制指定的解码器,即使有多个相同的codec_id.* 分流: 用户设置*/AVCodec *data_codec;/*** 在原数据头中,充当填充(分割)的字节数* 分流: 不使用* 混流: 用户可以通过av_format_set_metadata_header_padding设置.*/int metadata_header_padding;/*** 用户数据,这是用户的私有数据空间。*/void *opaque;/*** 设备用应用通讯的回调。*/av_format_control_message control_message_cb;/*** 输出时移,以微妙为单位。* 混流: 用户设置*/int64_t output_ts_offset;/*** 转储格式分隔符。可以是", " 或者 "\n" 等* - 混流: 用户设置* - 分流: 用户设置*/uint8_t *dump_separator;/*** 强制数据codec_id.* 分流操作: 用户设置*/enum AVCodecID data_codec_id;#if FF_API_OLD_OPEN_CALLBACKS/*** Called to open further IO contexts when needed for demuxing.** This can be set by the user application to perform security checks on* the URLs before opening them.* The function should behave like avio_open2(), AVFormatContext is provided* as contextual information and to reach AVFormatContext.opaque.** If NULL then some simple checks are used together with avio_open2().** Must not be accessed directly from outside avformat.* @See av_format_set_open_cb()** Demuxing: Set by user.** @deprecated Use io_open and io_close.*/attribute_deprecatedint (*open_cb)(struct AVFormatContext *s, AVIOContext **p, const char *url, int flags, const AVIOInterruptCB *int_cb, AVDictionary **options);#endif/*** ',' 符号分割的支持协议列表separated list of allowed protocols.* - 编码: 不使用* - 解码: 用户设置*/char *protocol_whitelist;/** A callback for opening new IO streams.** Whenever a muxer or a demuxer needs to open an IO stream (typically from* avformat_open_input() for demuxers, but for certain formats can happen at* other times as well), it will call this callback to obtain an IO context.** @param s the format context* @param pb on success, the newly opened IO context should be returned here* @param url the url to open* @param flags a combination of AVIO_FLAG_** @param options a dictionary of additional options, with the same*                semantics as in avio_open2()* @return 0 on success, a negative AVERROR code on failure** @note Certain muxers and demuxers do nesting, i.e. they open one or more* additional internal format contexts. Thus the AVFormatContext pointer* passed to this callback may be different from the one facing the caller.* It will, however, have the same 'opaque' field.*/int (*io_open)(struct AVFormatContext *s, AVIOContext **pb, const char *url,int flags, AVDictionary **options);/*** 将AVFormateContext.io_open()打开流关闭的回调。*/void (*io_close)(struct AVFormatContext *s, AVIOContext *pb);/*** ',' 符分割的不支持协议列表。* - 编码: 不使用* - 解码: 用户设置*/char *protocol_blacklist;/*** 最大streams数量* - 编码: 不使用* - 解码: 用户设定*/int max_streams;} AVFormatContext;

3.2、AVCodecContext

AVCodecContext 是 FFmpeg 库中的一个结构体,用于描述音视频编解码器的上下文信息。它包含了音视频编解码器的参数和状态,用于配置和控制编解码的过程。


typedef struct AVCodec {/*** 编解码器实现的名称。* 该名称是全局唯一的(但编码器和解码器可以共享名称)。* 这是从用户角度查找编解码器的主要方式。*/const char *name;/*** 编解码器的描述性名称,比前面的名称更具可读性。* 您应该使用NULL_IF_CONFIG_SMALL()宏来定义它。*/const char *long_name;enum AVMediaType type;//编解码器类型,视频,音频,或者字幕enum AVCodecID id;//全局唯一的编解码器ID/*** Codec capabilities.* see AV_CODEC_CAP_**/int capabilities;const AVRational *supported_framerates; ///支持帧率的数组,用于视频const enum AVPixelFormat *pix_fmts;     ///< 支持的像素格式数组,或者如果未知,则为NULL,数组以-1结尾。用于视频const int *supported_samplerates;       ///< 支持的音频采样率数组,或者如果未知,则为NULL,数组以0结尾。用于音频const enum AVSampleFormat *sample_fmts; ///<支持的采样数组,或者如果未知,则为NULL,数组以-1结尾。用于音频const uint64_t *channel_layouts;         ///< 支持声道数组,如果未知,则为NULL。 数组以0结尾,用于音频uint8_t max_lowres;                     ///< maximum value for lowres supported by the decoderconst AVClass *priv_class;              ///< 私有上下文的AVClassconst AVProfile *profiles;              ///< 已识别配置文件的数组,或者如果未知,则为NULL,数组以{FF_PROFILE_UNKNOWN}结尾/****************************************************************** 以下所有的字段都不是公共API,不可在libavcodec以外使用。以后新增字段都会放在上面。******************************************************************/int priv_data_size;//私有数据大小struct AVCodec *next;/*** @name Frame-level threading support functions* @{*//*** 如果已定义,则在创建线程上下文时调用它们。* 如果编解码器在init()中分配可写表,请在此处重新分配它们。* priv_data将被设置为原件的副本。*/int (*init_thread_copy)(AVCodecContext *);/*** Copy necessary context variables from a previous thread context to the current one.* If not defined, the next thread will start automatically; otherwise, the codec* must call ff_thread_finish_setup().** dst and src will (rarely) point to the same context, in which case memcpy should be skipped.*/int (*update_thread_context)(AVCodecContext *dst, const AVCodecContext *src);/** @} *//*** 私有编解码器默认值。*/const AVCodecDefault *defaults;/*** 初始化时从avcodec_register()调用的编解码器静态数据。*/void (*init_static_data)(struct AVCodec *codec);int (*init)(AVCodecContext *);int (*encode_sub)(AVCodecContext *, uint8_t *buf, int buf_size,const struct AVSubtitle *sub);/*** Encode data to an AVPacket.** @param      avctx          codec context* @param      avpkt          output AVPacket (may contain a user-provided buffer)* @param[in]  frame          AVFrame containing the raw data to be encoded* @param[out] got_packet_ptr encoder sets to 0 or 1 to indicate that a*                            non-empty packet was returned in avpkt.* @return 0 on success, negative error code on failure*/int (*encode2)(AVCodecContext *avctx, AVPacket *avpkt, const AVFrame *frame,int *got_packet_ptr);int (*decode)(AVCodecContext *, void *outdata, int *outdata_size, AVPacket *avpkt);int (*close)(AVCodecContext *);/*** Decode/encode API with decoupled packet/frame dataflow. The API is the* same as the avcodec_ prefixed APIs (avcodec_send_frame() etc.), except* that:* - never called if the codec is closed or the wrong type,* - AVPacket parameter change side data is applied right before calling*   AVCodec->send_packet,* - if AV_CODEC_CAP_DELAY is not set, drain packets or frames are never sent,* - only one drain packet is ever passed down (until the next flush()),* - a drain AVPacket is always NULL (no need to check for avpkt->size).*/int (*send_frame)(AVCodecContext *avctx, const AVFrame *frame);int (*send_packet)(AVCodecContext *avctx, const AVPacket *avpkt);int (*receive_frame)(AVCodecContext *avctx, AVFrame *frame);int (*receive_packet)(AVCodecContext *avctx, AVPacket *avpkt);/*** Flush buffers.* Will be called when seeking*/void (*flush)(AVCodecContext *);/*** Internal codec capabilities.* See FF_CODEC_CAP_* in internal.h*/int caps_internal;
} AVCodec;

3.3、AVPacket

AVPacket 是 FFmpeg 库中的一个结构体,用于存储音视频数据的压缩数据包。它是 FFmpeg 中处理音视频数据的重要数据结构之一。

AVPacket 结构体用于存储音视频数据的压缩数据,例如从容器中读取的音视频帧,或者编码后的音视频帧待写入容器。它包含了数据的时间戳、大小和相关信息,用于解码和编码过程中的数据处理。

在使用 AVPacket 时,可以通过 FFmpeg 的函数和接口进行创建、释放、填充数据等操作。例如,av_packet_alloc 函数用于分配一个新的 AVPacket 对象,av_packet_free 函数用于释放 AVPacket 对象,av_packet_ref 函数用于创建 AVPacket 的副本,av_packet_rescale_ts 函数用于对时间戳进行重新缩放等。

typedef struct AVPacket {AVBufferRef *buf; // data的buffer引用指针计数结构体int64_t pts; // 控制显示的pts时间int64_t dts; // 控制解码的dts时间uint8_t *data; // 媒体数据buffer的指针int   size; // 数据大小int   stream_index; // 流indexint   flags; // AV_PKT_FLAG值的组合AVPacketSideData *side_data; // 容器可以提供的附加数据包数据。 数据包可以包含几种类型的辅助信息。// AVStream-> time_base单位中此数据包的持续时间,如果未知则为0。 在演示顺序中等于next_pts  -  this_pts。int64_t duration;int64_t pos; // 流中的字节位置,如果未知则为-1
} AVPacket;

3.4、AVFrame

AVFrame 是 FFmpeg 库中的一个结构体,用于表示音视频帧的数据。它包含了一个音频帧或视频帧的各种信息,如像素数据、采样数据、时间戳等。

typedef struct AVStream {int index;    /**< 在AVFormatContext中的stream索引 *//*** 特定格式的stream id。* 解码: 由libavformat设定* 编码: 如果未设置,则由用户通过libavformat设置*/int id;#if FF_API_LAVF_AVCTX/*** @deprecated use the codecpar struct instead*/attribute_deprecatedAVCodecContext *codec;#endifvoid *priv_data;#if FF_API_LAVF_FRAC/*** @deprecated this field is unused*/attribute_deprecatedstruct AVFrac pts;#endif/*** 这是表示帧时间戳的基本时间单位(以秒为单位)。** 解码: libavformat设置* 编码: 可以在avformat_write_header()之前由调用者设置,以向混流器提供关于所需单位时间的* 提示。在avformat_write_header()中,混流器将用实际用于写入文件的时间戳(根据格式可能与* 用户提供的时间戳相关或不相关)的单位时间覆盖该字段。*/AVRational time_base;/*** 解码: 流显示序列中的第一帧pts时间,基于流时间(in stream time base.)。* 只有当你百分百确定该值就是真实的第一帧的pts时间,才可以设置它* 该值可能未定义(AV_NOPTS_VALUE).* @note The ASF header does NOT contain a correct start_time the ASF* 分流器禁止设置该值。*/int64_t start_time;/*** 解码: 流时长,基于流时间(in stream time base.)*      如果一个源文件指定了比特率,而未指定流时长,该值将由比特率和文件大小估算。** 编码: May be set by the caller before 用户可以在avformat_write_header()调用前设* 置,提示混流器估算时长*/int64_t duration;int64_t nb_frames;                 ///< 表示该流的已知帧数,或者为0int disposition; /**< AV_DISPOSITION_* 推荐比特字段 */enum AVDiscard discard; ///< 选择那些数据包可以被丢掉而不用被分流器分流。/*** 采样率(如果未知,该值为0)* - 编码: 用户设置.* - 解码: libavformat设置.*/AVRational sample_aspect_ratio;AVDictionary *metadata;//原数据信息/*** 平均帧率** - 分流: 在创建流时或者才函数avformat_find_stream_info()函数中可能被设置。* - 混流: 可能在avformat_write_header()函数调用前被设置*/AVRational avg_frame_rate;/*** 对于设置有AV_DISPOSITION_ATTACHED_PIC标志的流, 该数据包会包含该附加图片(专辑图片什么的)** 解码: libavformat设置, 不能被用户修改。* 编码: 不使用*/AVPacket attached_pic;/*** An array of side data that applies to the whole stream (i.e. the* container does not allow it to change between packets).** There may be no overlap between the side data in this array and side data* in the packets. I.e. a given side data is either exported by the muxer* (demuxing) / set by the caller (muxing) in this array, then it never* appears in the packets, or the side data is exported / sent through* the packets (always in the first packet where the value becomes known or* changes), then it does not appear in this array.** - demuxing: Set by libavformat when the stream is created.* - muxing: May be set by the caller before avformat_write_header().** Freed by libavformat in avformat_free_context().** @see av_format_inject_global_side_data()*/AVPacketSideData *side_data;/*** The number of elements in the AVStream.side_data array.*/int            nb_side_data;/*** 供用户检测流上发生的时间标志。 事件处理后,用户必须清除标志。 AVSTREAM_EVENT_FLAG_ *的组合。*/int event_flags;#define AVSTREAM_EVENT_FLAG_METADATA_UPDATED 0x0001 ///< The call resulted in updated metadata./******************************************************************该行下面的所有字段不是公共API的一部分。 它们不能在libavformat之外使用,并且可以随意更改和删*除。内部提示:请注意,物理删除这些字段将会破坏ABI。 用空字段替换已删除的字段,并向*AVStreamInternal添加新字段。******************************************************************//*** avformat_find_stream_info()函数使用的内部流信息*/#define MAX_STD_TIMEBASES (30*12+30+3+6)struct {int64_t last_dts;int64_t duration_gcd;int duration_count;int64_t rfps_duration_sum;double (*duration_error)[2][MAX_STD_TIMEBASES];int64_t codec_info_duration;int64_t codec_info_duration_fields;/*** 0  -> 解码器还未被检索到* >0 -> 解码器已被找到* <0 -> decoder with codec_id == -found_decoder has not been found*/int found_decoder;int64_t last_duration;/*** 这些字段用于估算平均帧率*/int64_t fps_first_dts;int     fps_first_dts_idx;int64_t fps_last_dts;int     fps_last_dts_idx;} *info;int pts_wrap_bits; /**< number of bits in pts (used for wrapping control) */// 时间戳生成支持:/*** 最后同步点的时间戳。** 当AVCodecParserContext.dts_sync_point >= 0 时初始化,并且接受一个当前容器的DTS。否* 则,AV_NOPTS_VALUE使用默认值*/int64_t first_dts;int64_t cur_dts;int64_t last_IP_pts;int last_IP_duration;/*** 编解码器探测缓存的数据包数量*/int probe_packets;/*** avformat_find_stream_info()调用期间,已经被分流的帧数*/int codec_info_nb_frames;/* av_read_frame() 支持 */enum AVStreamParseType need_parsing;struct AVCodecParserContext *parser;/*** 正在混流操作的流在数据包缓冲中的最后一个数据包*/struct AVPacketList *last_in_packet_buffer;AVProbeData probe_data;#define MAX_REORDER_DELAY 16int64_t pts_buffer[MAX_REORDER_DELAY+1];AVIndexEntry *index_entries; /**< 只有当格式不支持本地seeking时使用*/int nb_index_entries;unsigned int index_entries_allocated_size;/*** 流的真实基准帧率.* 这是所有时间戳可以准确表示的最低帧速率(它是流中所有帧速率的最小公倍数)。 请注意,这个值只* 是一个猜测! 例如,如果时基为1/90000,并且所有帧都具有约3600或1800个计时器滴答,则* r_frame_rate将为50/1。** avformat以外的代码应该使用此字段访问:* av_stream_get/set_r_frame_rate(stream)*/AVRational r_frame_rate;/*** 流标志符* 这是MPEG-TS流标识符 +1* 0 意味着未知*/int stream_identifier;int64_t interleaver_chunk_size;int64_t interleaver_chunk_duration;/*** 流探测状态* -1   -> 探测完毕*  0   -> 没有探测请求* rest -> 以request_probe作为接受的最低分数执行探测。* 不是公共API的一部分*/int request_probe;/*** 表示直到下一个关键帧的所有内容都应该丢弃。*/int skip_to_keyframe;/*** 在下一个数据包解码的帧开始时跳过的采样数。*/int skip_samples;/*** 如果不是0,则应从流的开始位置跳过的样本数量(样本从pts == 0的包中移除,这也假定负时间戳不会* 发生)。 旨在用于具有ad-hoc无间断音频支持的mp3等格式。*/int64_t start_skip_samples;/*** 如果不是0,应该从流中丢弃的第一个音频采样。 这是由设计丢弃的(需要全球采样计数),但无法避免* 由设计格式(如带有ad-hoc无间隙音频支持的mp3)破坏。*/int64_t first_discard_sample;/*** 在first_discard_sample之后打算丢弃的最后一个样本之后的样本。 仅适用于框架边界。 用于防止* 早期EOF,如果无间隙信息被破坏(考虑连接的MP3)。*/int64_t last_discard_sample;/*** 在libavformat内部使用的内部解码帧的数量不会访问其生存期,这与信息不同,因此它不在该结构* 中。*/int nb_decoded_frames;/*** 时间戳偏移添加到混流之前的时间戳* 非公共API*/int64_t mux_ts_offset;/*** 内部数据检查时间戳的包装*/int64_t pts_wrap_reference;/*** Options for behavior, when a wrap is detected.** Defined by AV_PTS_WRAP_ values.** If correction is enabled, there are two possibilities:* If the first time stamp is near the wrap point, the wrap offset* will be subtracted, which will create negative time stamps.* Otherwise the offset will be added.*/int pts_wrap_behavior;/*** 禁止执行两次update_initial_durations()的内部数据*/int update_initial_durations_done;/*** 内部数据,用于从pts生成dts*/int64_t pts_reorder_error[MAX_REORDER_DELAY+1];uint8_t pts_reorder_error_count[MAX_REORDER_DELAY+1];/*** 内部数据,用于分析DTS和检测错误的MPEG流*/int64_t last_dts_for_order_check;uint8_t dts_ordered;uint8_t dts_misordered;/*** Internal data to inject global side data*/int inject_global_side_data;/****************************************************************** 该行上方的所有字段都不是公共API的一部分。 下面的字段是公共API和ABI的一部分。******************************************************************//*** 包含键和值的一系列字符串,用于描述推荐的编码器配置。* 系列以 ','分割.* 键和值由'='分割.*/char *recommended_encoder_configuration;/*** 显示宽高比(如果未知,则为0)* - 编码: 不使用* - 解码: libavformat设置, 用于在内部计算显示宽高比。*/AVRational display_aspect_ratio;struct FFFrac *priv_pts;/*** libavformat内部使用的不透明字段。 不得以任何方式访问。*/AVStreamInternal *internal;/** 与此流关联的编解码器参数。 分别在avformat_new_stream()和avformat_free_context()* 中由libavformat分配和释放。** - 分流: 由libavformat在流创建时填充或在avformat_find_stream_info()赋值。* - 混流: 在avformat_write_header()之前由调用者填充*/AVCodecParameters *codecpar;} AVStream;

3.5、SwsContext

SwsContext 是 FFmpeg 库中的一个结构体,用于进行图像的缩放、颜色空间转换等操作。它是 FFmpeg 中的图像转换模块(swscale)的上下文。

SwsContext 结构体用于图像转换,适用于将一个像素格式的图像转换为另一个像素格式的图像。它可以用于图像缩放、色彩空间转换、图像格式转换等操作。

在 FFmpeg 4.0 版本及以后的版本中,SwsContext 结构体已经被弃用,取而代之的是 sws_getCachedContext 函数。该函数返回一个 struct SwsContext* 类型的指针,用于进行图像的缩放、颜色空间转换等操作。

struct SwsContext *sws_getCachedContext(struct SwsContext *context,int srcW, int srcH, enum AVPixelFormat srcFormat,int dstW, int dstH, enum AVPixelFormat dstFormat,int flags, SwsFilter *srcFilter, SwsFilter *dstFilter, const double *param);
  • int srcW:源图像的宽度。
  • int srcH:源图像的高度。
  • enum AVPixelFormat srcFormat:源图像的像素格式。
  • int dstW:目标图像的宽度。
  • int dstH:目标图像的高度。
  • enum AVPixelFormat dstFormat:目标图像的像素格式。
  • struct SwsFilter *srcFilter:源图像的过滤器。
  • struct SwsFilter *dstFilter:目标图像的过滤器。
  • float *param:用于可选参数的数组。
  • int paramCount:可选参数的数量。

使用 SwsContext 结构体进行图像转换的一般流程如下:
1、创建 SwsContext 对象:使用 sws_getContext 函数创建 SwsContext 对象,需要指定源图像的宽度、高度、像素格式,以及目标图像的宽度、高度、像素格式等参数。

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);

2、进行图像转换:使用 sws_scale 函数进行图像的实际转换,将源图像数据转换为目标图像数据。

int sws_scale(SwsContext *c, const uint8_t *const srcSlice[],const int srcStride[], int srcSliceY, int srcSliceH,uint8_t *const dst[], const int dstStride[]);

3、释放 SwsContext 对象:使用 sws_freeContext 函数释放 SwsContext 对象,释放相关资源。

void sws_freeContext(SwsContext *swsContext);

参考链接

https://blog.csdn.net/leixiaohua1020/article/details/14215833

https://blog.csdn.net/qq_25333681/category_7686458.html

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/219283.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

世微 锂电池保护IC DW01 充电器检测过充保护SOT23-6

一、 描述 DW01A 是一个锂电池保护电路&#xff0c;为避免锂电池因过充电、过放电、电流过大导致电池寿命缩短或电池被损坏而设计的。它具有高精确度的电压检测与时间延迟电路。 二、 主要特点 工作电流低 过充检测 4.3V&#xff0c;过充释放 4.05V&#xff1b; 过放检测 2.4…

使用Audition录制电脑内部声音

在电脑上播放的媒体文件&#xff0c;包括视频和声音&#xff0c;很多是可以播放却无法保存的。例如一些网页播放的视频&#xff0c;或者在线播放的音乐。 视频的话&#xff0c;可以使用工具来截图&#xff0c;抓取GIF或录屏。 声音的话&#xff0c;也可以使用工具进行录制。这里…

为养宠家庭量身打造,352 X63 Pet宠物专效空气净化器“养宠安馨,人宠共护”

当下,养宠人群日益增多,宠物在给家庭带来了欢乐的同时,也产生了一系列困扰,如何在健康环境中快乐养宠,成为很多家养宠家庭的新需求。成立于2014年的北京三五二环保科技有限公司是一家立足于家庭洁净空气和安全用水领域的科技创新型公司。以“安全、健康、舒适”等消费需求为核心…

uniapp - 简单版本自定义tab栏切换

tab切换是APP开发最常见的功能之一&#xff0c;uniapp中提供了多种形式的tab组件供我们使用。对于简单的页面而言&#xff0c;使用tabbar组件非常方便快捷&#xff0c;可以快速实现底部导航栏的效果。对于比较复杂的页面&#xff0c;我们可以使用tab组件自由定义样式和内容 目录…

Python框架批量数据抓取的高级教程

一、背景介绍 批量数据抓取是一种常见的数据获取方式&#xff0c;能够帮助我们快速、高效地获取网络上的大量信息。本文将介绍如何使用Python框架进行大规模抽象数据&#xff0c;以及如何处理这个过程中可能遇到的问题。 二、项目需求 我们将爬取大量知乎文章&#xff0c;讨…

vue使用el-tag完成添加标签操作

需求&#xff1a;做一个添加标签的功能&#xff0c;点击添加后输入内容后回车可以添加&#xff0c;并且标签可以删除 1.效果 2.主要代码讲解 鼠标按下后触发handleLabel函数&#xff0c;根据回车的keycode判断用户是不是按下的回车键&#xff0c;回车键键值为13&#xff0c;用…

HiveSql语法优化二 :join算法

Hive拥有多种join算法&#xff0c;包括Common Join&#xff0c;Map Join&#xff0c;Bucket Map Join&#xff0c;Sort Merge Buckt Map Join等&#xff0c;下面对每种join算法做简要说明&#xff1a; Common Join Common Join是Hive中最稳定的join算法&#xff0c;其通过一个M…

【MyBatis-Plus】常用的插件介绍(乐观锁、逻辑删除、分页)

&#x1f973;&#x1f973;Welcome Huihuis Code World ! !&#x1f973;&#x1f973; 接下来看看由辉辉所写的关于MyBatis-Plus的相关操作吧 目录 &#x1f973;&#x1f973;Welcome Huihuis Code World ! !&#x1f973;&#x1f973; 一.为什么要使用MyBatis-Plus中的插…

C语言学习----字符串数组和字符串指针

&#x1f308;在C Primer Plus 第六版中第11章的字符串和字符串函数讲的很好~ 对于字符串和字符串指针的用法又更深入了解了一些~ &#x1f42c; 本blog为 C Primer Plus 的记录~ ☘️对于字符串指针和数组更加深入了解~ &#x1f33a;省流&#xff1a;1️⃣字符串常量被储存在…

HNU-计算机网络-实验3-应用层和传输层协议分析(PacketTracer)

计算机网络 课程基础实验三应用层和传输层协议分析&#xff08;PacketTracer&#xff09; 计科210X 甘晴void 202108010XXX 【给助教的验收建议】 如果是助教&#xff0c;比起听同学读报告&#xff0c;更好的验收方式是随机抽取一个场景&#xff08;URL/HTTPS/FTP&#xff09…

Linux线程的设计

文章目录 一.理解Linux线程的本质进程地址空间是进程访问系统资源的窗口Linux系统中,线程是比进程更轻量级的执行流 二.Linux线程独立运行的原理三.基础线程控制 一.理解Linux线程的本质 进程地址空间是进程访问系统资源的窗口 Linux系统中,线程是比进程更轻量级的执行流 线程…

Mac 中文版 Navicat Premium 16 下载安装详细教程

哈喽朋友们大家好&#xff0c;今天做一期 Mac 数据库连接工具 Navicat Premium 16 的安装教程&#xff0c;很多朋友不知道怎么安装的&#xff0c;要不就是有试用期无法正常使用&#xff0c;要不就是英文的&#xff0c;改不了中文&#xff0c;大家可以跟着我的步骤安装&#xff…

RabbitMQ插件详解:rabbitmq_message_timestamp【Rabbitmq 五】

欢迎来到我的博客&#xff0c;代码的世界里&#xff0c;每一行都是一个故事 RabbitMQ时空之旅&#xff1a;rabbitmq_message_timestamp的奇妙世界 前言什么是rabbitmq_message_timestamprabbitmq_message_timestamp 的定义与作用&#xff1a;如何在 RabbitMQ 中启用消息时间戳&…

视觉检测系统在半导体行业的应用

一、半导体产业链概述 半导体产业链是现代电子工业的核心组成部分&#xff0c;涵盖了从原材料到最终产品的整个生产过程。这个产业链主要分为以下几个环节&#xff1a; 1.原材料供应&#xff1a;半导体行业的基石是半导体材料&#xff0c;如硅片、化合物半导体等。这些材料需要…

挑战52天学小猪佩奇笔记--day24

52天学完小猪佩奇--day24 ​【本文说明】 本文内容来源于对B站UP 脑洞部长 的系列视频 挑战52天背完小猪佩奇----day24 的视频内容总结&#xff0c;方便复习。强烈建议大家去关注一波UP&#xff0c;配合UP视频学习。 注&#xff1a;这集开始变成一段一段的猜台词&#xff0c;加…

python学习,1.变量和简单的数据类型

一、编写文章的目的 1.这是为了初学者而写的&#xff0c;学习python比较简单然后上手&#xff0c;也会过滤一些&#xff0c;如果没有提起到的&#xff0c;可以在学习的时候进行补充 2.相对来说&#xff0c;上手难度不会很难。 二、内容 1.让首字母大写&#xff1b;字母都大写…

STM32F103C8T6—烧录程序

STM32F103C8T6烧录程序方法 1. ST-Link烧录程序ST-Link软件下载ST-Link软件安装程序下载 2. 串口烧录程序CH340驱动下载安装连接程序下载 1. ST-Link烧录程序 该USB驱动程序(STSW-LINK009)适用于ST-LINK/V2, ST-LINK/V2-1和STLINK-V3板及其衍生物 首先下载ST-link驱动&#xf…

华为配置基本QinQ示例

组网需求 如图1所示&#xff0c;网络中有两个企业&#xff0c;企业1有两个分支&#xff0c;企业2有两个分支。这两个企业的各办公地的企业网都分别和运营商网络中的SwitchA和SwitchB相连&#xff0c;且公网中存在其它厂商设备&#xff0c;其外层VLAN Tag的TPID值为0x9100。 现…

解读unity内置的软阴影处理方式

解读unity内置的软阴影处理方式&#xff1a; 参考网址&#xff1a; https://blog.csdn.net/cgy56191948/article/details/105726682 https://blog.csdn.net/weixin_45776473/article/details/119582218 https://tajourney.games/5482/ 上面的博客已经论述了&#xff0c;为何出现…

5个免费、跨平台的SQLite数据库可视化工具

前言 SQLite是一个轻量级的嵌入式关系型数据库&#xff0c;目前最新的版本是 SQLite3。今天推荐5个实用的SQLite数据库可视化工具(GUI)&#xff0c;帮助大家更好的管理SQLite数据库。 什么是SQLite&#xff1f; SQLite是一个轻量级的嵌入式关系型数据库&#xff0c;它以一个…