基于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
相关文章:
基于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.Ur…...
利用tf-idf对特征进行提取
TF-IDF是一种文本特征提取的方法,用于评估一个词在一组文档中的重要性。 一、代码 from sklearn.feature_extraction.text import TfidfVectorizer import numpy as npdef print_tfidf_words(documents):"""打印TF-IDF矩阵中每个文档中非零值对应…...
遇到运维故障,有没有排查和解决故障的正确流程?
稳定是偶然,异常才是常态,用来标注IT运维工作再适合不过。 因为对于IT运维来说,工作最常遇到的就是不稳定性带来的各种故障,经常围绕发现故障、响应故障、定位故障、恢复故障这四大步。 故障处理是最心跳的事情,没有…...
javaWebssh汽车销售管理系统myeclipse开发mysql数据库MVC模式java编程计算机网页设计
一、源码特点 java ssh汽车销售管理系统是一套完善的web设计系统(系统采用ssh框架进行设计开发),对理解JSP java编程开发语言有帮助,系统具有完整的源代码和数据库,系统主要采用 B/S模式开发。开发环境为TOMCAT7.…...
基于pandoraNext使用chatgpt4
1.登陆GitHub 获取pandoraNext项目GitHub - pandora-next/deploy: Pandora Cloud Pandora Server Shared Chat BackendAPI Proxy Chat2API Signup Free PandoraNext. New GPTs(Gizmo) UI, All in one! 在release中选择相应版本操作系统的安装包进行下载 2.获取license_…...
12.视图
目录 1.视图的含义与作用 2.视图的创建与查看 1.创建视图的语法形式 2、查看视图: 1.使用DESCRIBE语句查看视图基本信息 2.使用SHOW TABLE STATUS语查看视图基本信息查看视图的信息 3.使用SHOW CREATE VIEW语查看视图详细信息 4.在views表中查看视图详细信息…...
Leetcode69 x的平方根
x的平方根 题解1 袖珍计算器算法题解2 二分查找题解3 牛顿迭代 给你一个非负整数 x ,计算并返回 x 的 算术平方根 。 由于返回类型是整数,结果只保留 整数部分 ,小数部分将被 舍去 。 注意:不允许使用任何内置指数函数和算符&…...
在Linux上安装配置Nginx高性能Web服务器
1 前言 Nginx是一个高性能的开源Web服务器,同时也可以作为反向代理服务器、负载均衡器、HTTP缓存以及作为一个邮件代理服务器。它以其出色的性能和灵活性而闻名,被广泛用于处理高流量的网站和应用程序。本文将介绍在Linux环境中安装Nginx的步骤…...
LeetCode 每日一题 Day 11||贪心
2697. 字典序最小回文串 给你一个由 小写英文字母 组成的字符串 s ,你可以对其执行一些操作。在一步操作中,你可以用其他小写英文字母 替换 s 中的一个字符。 请你执行 尽可能少的操作 ,使 s 变成一个 回文串 。如果执行 最少 操作次数的方…...
ocr表格文字识别软件怎么使用?
现在的OCR软件几乎是傻瓜式的设计,操作很简单,像金鸣识别的软件无论是网页版还是电脑客户端又或是小程序,界面都简单明了,用户只需提交待识别的图片,然后点击提交识别,等识别完成就直接打开或下载打开就行了…...
【QT 5 调试软件+Linux下调用脚本shell-经验总结+初步调试+基础样例】
【QT 5 调试软件Linux下调用脚本shell-经验总结初步调试基础样例】 1、前言2、实验环境3、自我总结4、实验过程(1)准备工作-脚本1)、准备工作-编写运行脚本文件2)、给权限3)、运行脚本 (2)进入q…...
使用 Goroutine 和 Channel 构建高并发程序
使用 Goroutine 和 Channel 构建高并发程序 文章目的与概要Golang 并发模型的重要性 Goroutine 和 Channel 的基础Goroutine:轻量级线程Channel:通信机制Goroutine 与 Channel 的协同工作 构建高并发模型的策略有效使用 Goroutine使用 Channel 进行数据传…...
大数据机器学习与深度学习——过拟合、欠拟合及机器学习算法分类
大数据机器学习与深度学习——过拟合、欠拟合及机器学习算法分类 过拟合,欠拟合 针对模型的拟合,这里引入两个概念:过拟合,欠拟合。 过拟合:在机器学习任务中,我们通常将数据集分为两部分:训…...
Lenovo联想拯救者Legion Y9000X 2021款(82BD)原装出厂Windows10系统
链接:https://pan.baidu.com/s/1GRTR7CAAQJdnh4tHbhQaDQ?pwdl42u 提取码:l42u 联想原厂WIN10系统自带所有驱动、出厂主题壁纸、系统属性专属LOGO标志、Office办公软件、联想电脑管家等预装程序 所需要工具:16G或以上的U盘 文件格式&am…...
pytorch中的transpose用法
注意:维数从0开始,0维 1维2维…,负数代表从右往左数,-1代表第一维,以此类推 import torch import numpy as np# 创建一个二维数组 arr torch.tensor([[[1, 2],[3, 4]],[[5, 6],[7, 8]]]) print("原始数组:"…...
SpringBoot面试题及答案(最新50道大厂版,持续更新)
在准备Spring Boot相关的面试题时,我发现网络上的资源往往缺乏深度和全面性。为了帮助广大Java程序员更好地准备面试,我花费了大量时间进行研究和整理,形成了这套Spring Boot面试题大全。 这套题库不仅包含了一系列经典的Spring Boot面试题及…...
KUKA机器人如何隐藏程序或程序段?
KUKA机器人如何隐藏程序或程序段? 如下图所示,新建一个示例程序进行说明, 如下图所示,如果红框中的动作指令不想让别人看到,想隐藏起来,如何做到? 如下图所示,在想要隐藏的程序或程序段的前后,分别添加 ;fold 和 endfold指令(这里要注意是英文状态下的输入法), 如…...
C++ STL(1)--概述
1. 简述 STL即标准模板库 Standard Template Library,包含了许多在计算机科学领域里所常用的基本数据结构和算法。STL具有高可重用性、高性能、高可移植性(跨平台)的优点。 两个特点: 1.1 数据结构和算法分离。 1.2 它不是面向对象的,是基于模…...
unity 2d 入门 飞翔小鸟 死亡闪烁特效(十三)
一、c#脚本 using System.Collections; using System.Collections.Generic; using UnityEngine;public class Bling : MonoBehaviour {public Texture img;public float speed;public static bool changeWhite false;private float alpha0f;// Start is called before the fi…...
Cannot find cache named ‘‘ for Builder Redis
当引入 Redissson 时,springCache 缓存机制失效 原因:springCache 默认使用本地缓存 Redisson 使用redis 缓存 最后都转成redis了。。。 总感觉哪不对 两者居然不共存...
eNSP-Cloud(实现本地电脑与eNSP内设备之间通信)
说明: 想象一下,你正在用eNSP搭建一个虚拟的网络世界,里面有虚拟的路由器、交换机、电脑(PC)等等。这些设备都在你的电脑里面“运行”,它们之间可以互相通信,就像一个封闭的小王国。 但是&#…...
理解 MCP 工作流:使用 Ollama 和 LangChain 构建本地 MCP 客户端
🌟 什么是 MCP? 模型控制协议 (MCP) 是一种创新的协议,旨在无缝连接 AI 模型与应用程序。 MCP 是一个开源协议,它标准化了我们的 LLM 应用程序连接所需工具和数据源并与之协作的方式。 可以把它想象成你的 AI 模型 和想要使用它…...
如何将联系人从 iPhone 转移到 Android
从 iPhone 换到 Android 手机时,你可能需要保留重要的数据,例如通讯录。好在,将通讯录从 iPhone 转移到 Android 手机非常简单,你可以从本文中学习 6 种可靠的方法,确保随时保持连接,不错过任何信息。 第 1…...
什么?连接服务器也能可视化显示界面?:基于X11 Forwarding + CentOS + MobaXterm实战指南
文章目录 什么是X11?环境准备实战步骤1️⃣ 服务器端配置(CentOS)2️⃣ 客户端配置(MobaXterm)3️⃣ 验证X11 Forwarding4️⃣ 运行自定义GUI程序(Python示例)5️⃣ 成功效果CDN 基础概念1. 定义2. 组成部分 (二)CDN 加速原理1. 请求路由2. 内容缓存3. 内容更新 (三)CDN 落地实践1. 选择 CDN 服务商2. 配置 CDN3. 集成到 Web 架构 …...
Java线上CPU飙高问题排查全指南
一、引言 在Java应用的线上运行环境中,CPU飙高是一个常见且棘手的性能问题。当系统出现CPU飙高时,通常会导致应用响应缓慢,甚至服务不可用,严重影响用户体验和业务运行。因此,掌握一套科学有效的CPU飙高问题排查方法&…...
Android第十三次面试总结(四大 组件基础)
Activity生命周期和四大启动模式详解 一、Activity 生命周期 Activity 的生命周期由一系列回调方法组成,用于管理其创建、可见性、焦点和销毁过程。以下是核心方法及其调用时机: onCreate() 调用时机:Activity 首次创建时调用。…...
音视频——I2S 协议详解
I2S 协议详解 I2S (Inter-IC Sound) 协议是一种串行总线协议,专门用于在数字音频设备之间传输数字音频数据。它由飞利浦(Philips)公司开发,以其简单、高效和广泛的兼容性而闻名。 1. 信号线 I2S 协议通常使用三根或四根信号线&a…...
C#中的CLR属性、依赖属性与附加属性
CLR属性的主要特征 封装性: 隐藏字段的实现细节 提供对字段的受控访问 访问控制: 可单独设置get/set访问器的可见性 可创建只读或只写属性 计算属性: 可以在getter中执行计算逻辑 不需要直接对应一个字段 验证逻辑: 可以…...
NPOI操作EXCEL文件 ——CAD C# 二次开发
缺点:dll.版本容易加载错误。CAD加载插件时,没有加载所有类库。插件运行过程中用到某个类库,会从CAD的安装目录找,找不到就报错了。 【方案2】让CAD在加载过程中把类库加载到内存 【方案3】是发现缺少了哪个库,就用插件程序加载进…...
