FFmpeg 播放 YUV
1. 创建分类
1.1 创建 View 分类
1) 创建头文件,UIView Category.h
-
-
-
NS_ASSUME_NONNULL_BEGIN
-
-
@interface UIView (Category)
-
@property (nonatomic, assign) CGFloat x;
-
@property (nonatomic, assign) CGFloat y;
-
@property (nonatomic, assign) CGFloat width;
-
@property (nonatomic, assign) CGFloat height;
-
@property (nonatomic, assign) CGFloat centerX;
-
@property (nonatomic, assign) CGFloat centerY;
-
@property (nonatomic, assign) CGSize size;
-
@property (nonatomic, assign) CGPoint origin;
-
@end
-
-
NS_ASSUME_NONNULL_END
2) 实现头文件,UIView Category.m
-
-
-
@implementation UIView (Category)
-
-
- (void)setX:(CGFloat)x
-
{
-
CGRect frame = self.frame;
-
frame.origin.x = x;
-
self.frame = frame;
-
}
-
-
- (void)setY:(CGFloat)y
-
{
-
CGRect frame = self.frame;
-
frame.origin.y = y;
-
self.frame = frame;
-
}
-
-
- (CGFloat)x
-
{
-
return self.frame.origin.x;
-
}
-
-
- (CGFloat)y
-
{
-
return self.frame.origin.y;
-
}
-
-
- (void)setCenterX:(CGFloat)centerX
-
{
-
CGPoint center = self.center;
-
center.x = centerX;
-
self.center = center;
-
}
-
-
- (CGFloat)centerX
-
{
-
return self.center.x;
-
}
-
-
- (void)setCenterY:(CGFloat)centerY
-
{
-
CGPoint center = self.center;
-
center.y = centerY;
-
self.center = center;
-
}
-
-
- (CGFloat)centerY
-
{
-
return self.center.y;
-
}
-
-
- (void)setWidth:(CGFloat)width
-
{
-
CGRect frame = self.frame;
-
frame.size.width = width;
-
self.frame = frame;
-
}
-
-
- (void)setHeight:(CGFloat)height
-
{
-
CGRect frame = self.frame;
-
frame.size.height = height;
-
self.frame = frame;
-
}
-
-
- (CGFloat)height
-
{
-
return self.frame.size.height;
-
}
-
-
- (CGFloat)width
-
{
-
return self.frame.size.width;
-
}
-
-
- (void)setSize:(CGSize)size
-
{
-
CGRect frame = self.frame;
-
frame.size = size;
-
self.frame = frame;
-
}
-
-
- (CGSize)size
-
{
-
return self.frame.size;
-
}
-
-
- (void)setOrigin:(CGPoint)origin
-
{
-
CGRect frame = self.frame;
-
frame.origin = origin;
-
self.frame = frame;
-
}
-
-
- (CGPoint)origin
-
{
-
return self.frame.origin;
-
}
-
-
@end
1.2 创建 Image 分类
1) 创建头文件,UIImage Category.h
-
-
-
NS_ASSUME_NONNULL_BEGIN
-
-
@interface UIImage (Category)
-
-
(instancetype)withColor:(UIColor *)color;
-
-
(instancetype)imageFromRGB:(const UInt8 *)data linesize:(int)linesize width:(int)width height:(int)height;
-
-
(instancetype)withScreenShotForView:(UIView *)view;
-
-
(instancetype)withPhotoForView:(UIView *)view;
-
-
@end
-
-
NS_ASSUME_NONNULL_END
2) 实现头文件,UIImage Category.m
-
-
-
-
-
@implementation UIImage (Category)
-
-
/// 颜色转图片
-
/// - Parameter color: 色值
-
(instancetype)withColor:(UIColor *)color{
-
CGRect rect = CGRectMake(0.0f, 0.0f, 1.0f, 1.0f);
-
UIGraphicsBeginImageContext(rect.size);
-
CGContextRef context = UIGraphicsGetCurrentContext();
-
CGContextSetFillColorWithColor(context, [color CGColor]);
-
CGContextFillRect(context, rect);
-
UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
-
UIGraphicsEndImageContext();
-
return image;
-
}
-
-
/// RGB 数据转换Image
-
/// - Parameters:
-
/// - buffer: RGB 数据
-
/// - linesize: 行大小
-
/// - width: 宽
-
/// - height: 高
-
(instancetype)imageFromRGB:(const UInt8 *)buffer linesize:(int)linesize width:(int)width height:(int)height{
-
CGBitmapInfo bitmapInfo = kCGBitmapByteOrderDefault;
-
CFDataRef dataRef = CFDataCreateWithBytesNoCopy(kCFAllocatorDefault, buffer, linesize * height,kCFAllocatorNull);
-
CGDataProviderRef provider = CGDataProviderCreateWithCFData(dataRef);
-
CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
-
CGImageRef cgImage = CGImageCreate(width,
-
height,
-
8,
-
24,
-
linesize,
-
colorSpace,
-
bitmapInfo,
-
provider,
-
NULL,
-
NO,
-
kCGRenderingIntentDefault);
-
CGColorSpaceRelease(colorSpace);
-
UIImage *image = [UIImage imageWithCGImage:cgImage];
-
CGImageRelease(cgImage);
-
CGDataProviderRelease(provider);
-
CFRelease(dataRef);
-
return image;
-
}
-
-
/// View转图片 截取view生成图片
-
/// - Parameter view: 视图转图片
-
(instancetype)withScreenShotForView:(UIView *)view{
-
UIImage *image = nil;
-
CGSize size = CGSizeMake(view.width, view.height);
-
UIGraphicsBeginImageContextWithOptions(size, YES, [UIScreen mainScreen].scale);
-
[view.layer renderInContext:UIGraphicsGetCurrentContext()];
-
image = UIGraphicsGetImageFromCurrentImageContext();
-
UIGraphicsEndImageContext();
-
/*CGImageRef imageRef = CGImageCreateWithImageInRect([image CGImage], rect);
-
image = [UIImage imageWithCGImage:imageRef];
-
CGImageRelease(imageRef);*/
-
return image;
-
}
-
-
/// 系统图片转图片加提示音
-
/// - Parameter view: 视图转图片
-
(instancetype)withPhotoForView:(UIView *)view{
-
UIGraphicsBeginImageContextWithOptions(view.bounds.size, NO, 0);
-
//UIGraphicsBeginImageContext(view.bounds.size);
-
[view.layer renderInContext:UIGraphicsGetCurrentContext()];
-
UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
-
UIGraphicsEndImageContext();
-
// UIImageWriteToSavedPhotosAlbum(img, self, nil, nil);
-
AudioServicesPlaySystemSound(kSystemSoundID_Vibrate);
-
SystemSoundID soundId;
-
NSString *path = @"/System/Library/Audio/UISounds/photoShutter.caf";
-
//[[NSBundle mainBundle]pathForResource:music ofType:@"mp3"];
-
AudioServicesCreateSystemSoundID((__bridge CFURLRef)[NSURL fileURLWithPath:path], &soundId);
-
AudioServicesPlaySystemSound(soundId);
-
return image;
-
}
-
-
@end
2. 封装播放 YUV 管理器
2.1 创建头文件,YUVPlayer.h
-
-
-
-
-
NS_ASSUME_NONNULL_BEGIN
-
-
/// 定义回调 Delegate
-
@protocol YUVPlayerDelegate <NSObject>
-
-
@optional
-
-(void)showImage:(UIImage *) image;
-
@end
-
-
-
@interface YUVPlayer : NSObject
-
-
@property(nonatomic, assign)id<YUVPlayerDelegate> delegate;
-
-
- (int)init:(int) width height:(int) height;
-
-
- (void)cacheData:(uint8_t *)data withSize:(int) size;
-
-
@end
-
-
NS_ASSUME_NONNULL_END
2.2 实现头文件,YUVPlayer.m
-
-
-
-
-
-
-
@interface YUVPlayer(){
-
AVFrame *frame_yuv;
-
AVFrame *frame_rgb;
-
struct SwsContext *sws_ctx;
-
uint8_t *buffer_src;
-
int buffer_size;
-
int data_remain_size;
-
int write_remain_size;
-
}
-
@end
-
-
-
@implementation YUVPlayer
-
-
-
/// 初始化
-
/// - Parameters:
-
/// - width: 宽
-
/// - height: 高
-
- (int)init:(int)width height:(int)height{
-
frame_yuv = NULL;
-
frame_rgb = NULL;
-
sws_ctx = NULL;
-
buffer_src = NULL;
-
buffer_size = 0;
-
data_remain_size = 0;
-
write_remain_size = 0;
-
-
// 创建需要编码的一帧
-
frame_yuv = av_frame_alloc();
-
if(!frame_yuv){
-
fprintf(stderr, "av_frame_alloc function frame_yuv failed. \n");
-
return -1;
-
}
-
// 设置原始帧 YUV 宽高 848 480
-
frame_yuv->width = width;
-
frame_yuv->height = height;
-
// 设置 YUV 格式
-
frame_yuv->format = AV_PIX_FMT_YUV420P;
-
//ret = av_frame_get_buffer(frame_src, 1);//按 32 位对齐
-
-
// yuv 数据 根据需要的解码类型,获取需要的 buffer,结束 free 掉数据
-
buffer_size = av_image_get_buffer_size(frame_yuv->format, frame_yuv->width, frame_yuv->height, 1);
-
buffer_src = av_malloc(buffer_size);//YUV420P: 610560
-
-
-
// 根据指定的图像参数和提供的数组,设置数据指针和行数,数据填充到 frame_yuv 里面
-
//av_image_fill_arrays(frame_yuv->data, frame_yuv->linesize, outBuffer, frame_yuv->format, codec_ctx->width, codec_ctx->height, 1);//32
-
//RGB24: 1221120
-
// 初始化转换帧
-
frame_rgb = av_frame_alloc();
-
if(!frame_rgb){
-
fprintf(stderr, "av_frame_alloc function frame_rgb failed. \n");
-
return -1;
-
}
-
// 设置转换 RGB 帧的宽高
-
frame_rgb->width = width;
-
frame_rgb->height = height;
-
// 设置 RGB 格式
-
frame_rgb->format = AV_PIX_FMT_RGB24;
-
// 赋值填充帧数据
-
av_image_alloc(frame_rgb->data, frame_rgb->linesize, frame_rgb->width, frame_rgb->height, frame_rgb->format, 1);
-
// 获取转换帧内容
-
sws_ctx = sws_getContext(frame_yuv->width, frame_yuv->height, frame_yuv->format, frame_rgb->width, frame_rgb->height,
-
frame_rgb->format, SWS_FAST_BILINEAR, NULL, NULL, NULL);
-
if(!sws_ctx){
-
fprintf(stderr, "sws_getContext function failed. \n");
-
return -1;
-
}
-
printf("YUVToRGBShow init success.\n");
-
return 0;
-
}
-
-
/// 缓存 YUV 数据
-
/// - Parameters:
-
/// - data: YUV 数据
-
/// - size: YUV 数据大小
-
- (void)cacheData:(uint8_t *)data withSize:(int)size{
-
// 传递过来的数据当前大小
-
int current_size = (int)size;
-
// 每次只能编码 YUV 数据的大小
-
int yuv_length = buffer_size;
-
// 累计传递过来的数据,每次用了多少数据
-
write_remain_size = 0;
-
while (current_size > 0) {
-
-
if(current_size >= yuv_length){
-
// 当前剩余的数据大于或者等于解码设定的数据
-
// 当循环进行编码时,传过来的余留数据大于或者等于 YUV 设定长度的时候,余留数据为 0
-
if(data_remain_size >= yuv_length){
-
data_remain_size = 0;
-
}
-
// 需要编码的数据长度
-
int copy_length = yuv_length - data_remain_size;
-
// 取前面等于 YUV 数据的大小
-
memcpy(buffer_src data_remain_size, data write_remain_size, copy_length);
-
// 获取完整一帧数据,进行编码
-
[self yuvToImage:buffer_src withSize: yuv_length];
-
// 当前的数据的大小
-
current_size -= copy_length;
-
// 写了多少数据
-
write_remain_size = copy_length;
-
// 余留多少数据
-
data_remain_size = current_size;
-
-
}else if(size >= yuv_length){
-
// 接收的数据大于或者等于解码设定的数据时,余留的数据进行缓存
-
// 如果余留的数据小于编码的数据,缓存数据
-
memcpy(buffer_src, data write_remain_size, data_remain_size);
-
// 当前的数据为 0 current_size == data_remain_size
-
current_size -= data_remain_size;
-
-
}else{
-
// 传过来的数据小于 YUV 设定长度的时候,进行缓存,大于或者等于时候,进行编码和缓存
-
// 当前数据的总长度
-
int sum_length = data_remain_size current_size;
-
// 总长度大于或者等于 YUV 设置长度的时候
-
if(sum_length >= yuv_length){
-
// 需要拷贝的长度
-
int copy_length = yuv_length - data_remain_size;
-
// 拷贝需要编码的数据
-
memcpy(buffer_src data_remain_size , data, copy_length);
-
// 获取完整一帧数据,进行编码
-
[self yuvToImage: buffer_src withSize: yuv_length];
-
// 当前数据剩余的大小
-
current_size -= copy_length;
-
// 写了多少数据
-
write_remain_size = copy_length;
-
// 余留数据用完,置为0
-
data_remain_size = 0;
-
}else{
-
// 当传过来的数据小于设定编码数据大小的时候,缓存传过来的数据
-
memcpy(buffer_src data_remain_size, data write_remain_size, current_size);
-
// 每次缓存余留数据大小的累加
-
data_remain_size = current_size;
-
// 当前数据已经缓存时,当前数据大小置为0
-
current_size = 0;
-
}
-
}
-
}
-
}
-
-
/// YUV 转换 RGB ,RGB 转换 Image
-
/// - Parameters:
-
/// - data: YUV 数据
-
/// - size: YUV 数据大小
-
- (void)yuvToImage:(const uint8_t *)data withSize:(int)size{
-
// 内存自动释放池
-
@autoreleasepool {
-
// 填充 frame_yuv
-
int ret = av_image_fill_arrays(frame_yuv->data, frame_yuv->linesize, data, frame_yuv->format, frame_yuv->width, frame_yuv->height, 1);//按几位对齐
-
if(ret == 0){
-
fprintf(stderr, "av_image_fill_arrays function failed. \n");
-
return;
-
}
-
// 转换 RGB
-
ret = sws_scale(sws_ctx, (const uint8_t **)frame_yuv->data, frame_yuv->linesize, 0, frame_rgb->height, frame_rgb->data, frame_rgb->linesize);
-
if(ret == 0){
-
fprintf(stderr, "sws_scale function failed. \n");
-
return;
-
}
-
// 根据 RGB 数据生成 Image
-
UIImage *image = [UIImage imageFromRGB:frame_rgb->data[0] linesize:frame_rgb->linesize[0] width:frame_rgb->width height:frame_rgb->height];
-
if(!image){
-
fprintf(stderr, "RGB convert image failed. \n");
-
return;
-
}
-
// 返回 Image
-
if([self.delegate respondsToSelector:@selector(showImage:)]){
-
// 返回 Image
-
[self.delegate showImage:image];
-
// 睡眠 30 毫秒
-
usleep(30 * 1000);
-
}
-
}
-
}
-
-
/// 释放播放器数据
-
- (void) close{
-
if(sws_ctx){
-
sws_freeContext(sws_ctx);
-
sws_ctx = NULL;
-
}
-
if(frame_yuv){
-
av_frame_free(&frame_yuv);
-
frame_yuv = NULL;
-
}
-
if(frame_rgb){
-
av_frame_free(&frame_rgb);
-
frame_rgb = NULL;
-
}
-
if(buffer_src){
-
av_free(buffer_src);
-
buffer_src = NULL;
-
}
-
printf("YUVToRGBShow close.\n");
-
}
-
-
/// 释放数据
-
- (void)dealloc{
-
[self close];
-
}
-
-
@end
3. 测试函数及效果图
3.1 测试函数 onClickYUVPlayer,ViewController.m
-
-
-
-
-
-
-
-
-
-
@interface ViewController ()<YUVPlayerDelegate>
-
@property(nonatomic, strong) UIImageView *imageView;
-
@end
-
-
@implementation ViewController
-
@synthesize imageView;
-
-
- (void)viewDidLoad {
-
[super viewDidLoad];
-
}
-
-
/// 分离视频文件
-
/// - Parameter sender: UI 按钮
-
- (IBAction)onClickMediaFileSeparation:(id)sender {
-
// 输入文件
-
NSString * inFilePath = [PathTool bundlePath:@"input.mp4"];
-
// 输出文件(一般 mp4 音频流为 aac 格式 "aac" = "adts")
-
NSString * outFilePathAudio = [PathTool documentsPath:@"output"];
-
// 输出文件(一般 mp4 视频流为 h264 或者 hevc)
-
NSString * outFilePathVideo = [PathTool documentsPath:@"output"];
-
// 创建分离视频文件类
-
__block MediaFileSeparation *mediaFileSeparation = [[MediaFileSeparation alloc] init];
-
// 初始化数据,传入输入输出文件
-
int ret = [mediaFileSeparation init: inFilePath.UTF8String videoFilePath: (char *)outFilePathAudio.UTF8String audioFilePath: (char *)outFilePathVideo.UTF8String];
-
// 当返回值小于 0 时,初始化失败
-
if(ret < 0){
-
return;
-
}
-
dispatch_async(dispatch_get_global_queue(DISPATCH_BLOCK_DETACHED, 0), ^{
-
// 循环拆分数据
-
[mediaFileSeparation convertFrames];
-
// 结束
-
[mediaFileSeparation stop];
-
// 释放数据
-
mediaFileSeparation = nil;
-
});
-
}
-
-
/// H264 解码 YUV
-
/// - Parameter sender: UI 按钮
-
- (IBAction)onClickH264ToYUV:(id)sender {
-
// 输入文件
-
NSString *inputFilePath = [PathTool documentsPath:@"output.hevc"];
-
// 输出文件
-
NSString *outputFilePath = [PathTool documentsPath:@"output.yuv"];
-
// 初始化文件管理器
-
__block FileTool *fileTool = [[FileTool alloc] init];
-
// 打开文件
-
int ret = (int)[fileTool open:inputFilePath.UTF8String model: FILE_MODE_READ];
-
// 小于 0,打开文件异常
-
if(ret < 0){
-
return;
-
}
-
// 初始化 H264 解码 YUV 管理器
-
__block H264ToYUVDeCoder *h264ToYUVDeCoder = [[H264ToYUVDeCoder alloc]init];
-
// 传入输入输出文件路径
-
ret = [h264ToYUVDeCoder init:inputFilePath.UTF8String outputFilePath:outputFilePath.UTF8String];
-
// 当返回值小于 0 时,初始化失败
-
if(ret < 0){
-
return;
-
}
-
//Stream #0:0: Video: hevc, yuv420p(tv), 848x480, 25 fps, 25 tbr, 1200k tbn, 25 tbc
-
// __weak ViewController *vc = self;
-
// 开启子线程,进行解码操作
-
dispatch_async(dispatch_get_global_queue(DISPATCH_BLOCK_DETACHED, 0), ^{
-
// 定义输入读取数据大小
-
int length = 1024 * 6;
-
uint8_t frame[length];
-
while(true){
-
// 读取输入数据
-
int size = (int)[fileTool read:frame length:length];
-
// 读完文件,则跳出循环
-
if(size <= 0){
-
break;
-
}
-
// 解码数据
-
[h264ToYUVDeCoder convertToYUV:frame withSize:length];
-
}
-
// 停止
-
[h264ToYUVDeCoder stop];
-
// 释放解码器数据
-
h264ToYUVDeCoder = nil;
-
// 释放文件资源
-
fileTool = nil;
-
});
-
}
-
-
/// YUV 编码 H264
-
/// - Parameter sender: UI 按钮
-
- (IBAction)onClickYUVToH264:(id)sender {
-
// 输入文件
-
NSString *inputFilePath = [PathTool documentsPath:@"output.yuv"];
-
// 输出文件
-
NSString *outputFilePath = [PathTool documentsPath:@"output.h264"];
-
// 初始化文件管理器
-
__block FileTool *fileTool = [[FileTool alloc]init];
-
// 打开文件
-
int ret = (int)[fileTool open:inputFilePath.UTF8String model:FILE_MODE_READ];
-
// 当返回值小于 0 时,初始化失败
-
if(ret < 0){
-
return;
-
}
-
// 初始化 YUV 编码 H264 管理器
-
__block YUVToH264EnCoder *yuvToH264EnCoder = [[YUVToH264EnCoder alloc]init];
-
// 传入输出文件路径,宽和高
-
ret = [yuvToH264EnCoder init: outputFilePath.UTF8String width:848 height:480];
-
// 当返回值小于 0 时,初始化失败
-
if(ret < 0){
-
return;
-
}
-
// 开启子线程,进行编码操作
-
dispatch_async(dispatch_get_global_queue(DISPATCH_BLOCK_DETACHED, 0), ^{
-
// 定义输入读取数据大小,如果根据初始化得到的 yuv 数据大小,读取 610560 个数据点,fread 会卡住,只能分段读取进行缓存处理
-
int length = 1024 * 8;
-
uint8_t frame[length];
-
while (true) {
-
// 读取输入数据
-
int size = (int)[fileTool read:frame length:length];
-
// 读完文件,则跳出循环
-
if(size <= 0){
-
break;
-
}
-
[yuvToH264EnCoder convertCacheData:frame withSize:size];
-
}
-
// 停止
-
[yuvToH264EnCoder stop];
-
// 释放解码器数据
-
yuvToH264EnCoder = nil;
-
// 释放文件资源
-
fileTool = nil;
-
});
-
}
-
-
/// 播放 YUV
-
/// - Parameter sender: UI 按钮
-
- (IBAction)onClickYUVPlayer:(id)sender {
-
// 输入文件
-
NSString *inputFilePath = [PathTool documentsPath:@"output.yuv"];
-
// 初始化文件管理器
-
__block FileTool *fileTool = [[FileTool alloc] init];
-
// 打开文件
-
int ret = (int)[fileTool open:inputFilePath.UTF8String model:FILE_MODE_READ];
-
// 返回值小于 0,打开文件失败
-
if(ret < 0){
-
return;
-
}
-
// 初始化 YUV 转 RGB 管理器
-
__block YUVPlayer *yuvPlayer = [[YUVPlayer alloc]init];
-
// delegate 回调
-
yuvPlayer.delegate = self;
-
// 添加 ImageView,用来播放画面
-
[self setupImageView];
-
// 传入宽高
-
ret = [yuvPlayer init:848 height:480];
-
// 返回值小于 0,初始化失败
-
if(ret < 0){
-
return;
-
}
-
// 开启子线程进行转码渲染
-
dispatch_async(dispatch_get_global_queue(DISPATCH_BLOCK_DETACHED, 0), ^{
-
// 定义输入读取数据大小
-
int length = 1024 * 8;
-
uint8_t frame[length];
-
while (true) {
-
// 读取输入数据
-
int size = (int)[fileTool read:frame length:length];
-
// 读完文件,则跳出循环
-
if(size <= 0){
-
break;
-
}
-
// 缓存并播放数据
-
[yuvPlayer cacheData:frame withSize:size];
-
}
-
// 释放播放器数据
-
yuvPlayer = nil;
-
// 释放文件资源
-
fileTool = nil;
-
});
-
}
-
-
// ImageView
-
-(void)setupImageView{
-
if(!imageView){
-
CGFloat width = 320;
-
CGFloat height = 180;
-
CGFloat x = (self.view.width - width) * 0.5;
-
CGFloat y = (self.view.height - height) * 0.85;
-
// 显示画面
-
imageView = [[UIImageView alloc]initWithFrame:CGRectMake(x, y, width, height)];
-
imageView.backgroundColor = UIColor.blackColor;
-
imageView.contentMode = UIViewContentModeScaleToFill;
-
[self.view addSubview:imageView];
-
}
-
}
-
-
// 更新 Image
-
- (void)showImage:(UIImage *)image{
-
dispatch_async(dispatch_get_main_queue(), ^{
-
self.imageView.image = image;
-
});
-
}
-
-
@end
3.2 效果图:
这篇好文章是转载于:学新通技术网
- 版权申明: 本站部分内容来自互联网,仅供学习及演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,请提供相关证据及您的身份证明,我们将在收到邮件后48小时内删除。
- 本站站名: 学新通技术网
- 本文地址: /boutique/detail/tanhfhkabb
系列文章
更多
同类精品
更多
-
photoshop保存的图片太大微信发不了怎么办
PHP中文网 06-15 -
Android 11 保存文件到外部存储,并分享文件
Luke 10-12 -
word里面弄一个表格后上面的标题会跑到下面怎么办
PHP中文网 06-20 -
《学习通》视频自动暂停处理方法
HelloWorld317 07-05 -
微信公众号没有声音提示怎么办
PHP中文网 03-31 -
photoshop扩展功能面板显示灰色怎么办
PHP中文网 06-14 -
excel下划线不显示怎么办
PHP中文网 06-23 -
excel打印预览压线压字怎么办
PHP中文网 06-22 -
怎样阻止微信小程序自动打开
PHP中文网 06-13 -
photoshop蒙版画笔没反应怎么办
PHP中文网 06-24