From 487bfc2392f74f19115ab24c129f3513afb2db1a Mon Sep 17 00:00:00 2001 From: NhatHM Date: Tue, 2 Jul 2019 17:16:20 +0700 Subject: [PATCH 1/2] Add record func --- init-ios.sh | 6 +- .../IJKFFMoviePlayerController.m | 255 ++++++++++++++++++ .../IJKMediaPlayer/IJKMediaPlayback.h | 11 + 3 files changed, 269 insertions(+), 3 deletions(-) diff --git a/init-ios.sh b/init-ios.sh index 41a25dc1a5..6752c354f0 100755 --- a/init-ios.sh +++ b/init-ios.sh @@ -39,9 +39,9 @@ fi set -e TOOLS=tools -FF_ALL_ARCHS_IOS6_SDK="armv7 armv7s i386" -FF_ALL_ARCHS_IOS7_SDK="armv7 armv7s arm64 i386 x86_64" -FF_ALL_ARCHS_IOS8_SDK="armv7 arm64 i386 x86_64" +#FF_ALL_ARCHS_IOS6_SDK="armv7 armv7s i386" +#FF_ALL_ARCHS_IOS7_SDK="armv7 armv7s arm64 i386 x86_64" +FF_ALL_ARCHS_IOS8_SDK="arm64 i386 x86_64" FF_ALL_ARCHS=$FF_ALL_ARCHS_IOS8_SDK FF_TARGET=$1 diff --git a/ios/IJKMediaPlayer/IJKMediaPlayer/IJKFFMoviePlayerController.m b/ios/IJKMediaPlayer/IJKMediaPlayer/IJKFFMoviePlayerController.m index 5263daae27..a622e8f91a 100644 --- a/ios/IJKMediaPlayer/IJKMediaPlayer/IJKFFMoviePlayerController.m +++ b/ios/IJKMediaPlayer/IJKMediaPlayer/IJKFFMoviePlayerController.m @@ -761,6 +761,261 @@ - (UIImage *)thumbnailImageAtCurrentTime return nil; } +#pragma --mark 录制视频(只能录制RTSP流)格式mov +- (int)rtsp2mov:(NSString *)streamURL storageFilePath:(NSString *)filePath isStop:(BOOL)bStop +{ + // 输入 + AVStream *i_video_stream = NULL; //视频 + AVStream *i_audio_stream = NULL; //音频 + AVFormatContext *i_fmt_ctx = NULL; + + // 输出 + AVStream *o_video_stream = NULL; //视频 + AVStream *o_audio_stream = NULL; //音频 + AVFormatContext *o_fmt_ctx = NULL; + + // ijkmp_global_init(); + // // FFMPEG注册 + // ijkav_register_all(); + // avcodec_register_all(); + // av_register_all(); + // if(avformat_network_init() != 0){ + // printf("avformat_network_init() 失败 \n"); + // return -1; + // } + + // 视频存储的路径 + const char *filename = [filePath cStringUsingEncoding:NSUTF8StringEncoding]; + // 打开网络流或文件流 + if (avformat_open_input(&i_fmt_ctx, [streamURL cStringUsingEncoding:NSUTF8StringEncoding], NULL, NULL)!=0) + { + printf("无法打开输入流.\n"); + return -1; + } + + if (avformat_find_stream_info(i_fmt_ctx, NULL)<0) + { + printf("找不到流信息.\n"); + return -1; + } + + // 列出输入文件的相关流信息 + printf("---------------- 输入文件信息 ---------------\n"); + av_dump_format(i_fmt_ctx, 0, [streamURL cStringUsingEncoding:NSUTF8StringEncoding], 0); + printf("-------------------------------------------------\n"); + + + /* find first video stream */ + for (unsigned i=0; inb_streams; i++) + { + if (i_fmt_ctx->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) { + // 视频 + i_video_stream = i_fmt_ctx->streams[i]; + } + else if(i_fmt_ctx->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_AUDIO){ + // 音频 + i_audio_stream = i_fmt_ctx->streams[i]; + } + } + + if (i_video_stream == NULL) + { + fprintf(stderr, "没有找到任何视频流媒体\n"); + return -1; + } + + AVOutputFormat *oformat = av_guess_format(NULL, filename, NULL); + /** + * avformat_alloc_output_context2(AVFormatContext **ctx, AVOutputFormat *oformat, const char *format_name, const char *filename) + * + * ctx: 函数调用成功之后创建的AVFormatContext结构体 + * oformat: 指定AVFormatContext中的AVOutputFormat,用于确定输出格式。如果指定为NULL,可以设定后两个参数(format_name或者filename)由FFmpeg猜测输出格式 + * format_name: 指定输出格式的名称。根据格式名称,FFmpeg会推测输出格式。输出格式可以是“flv”,“mkv”等等 + * filename: 指定输出文件的名称 + */ + if(avformat_alloc_output_context2(&o_fmt_ctx, oformat, NULL, filename) !=0){ + fprintf(stderr, "初始化o_fmt_ctx结构体失败\n"); + return -1; + } + + /**************************************** 视频 **********************************/ + o_video_stream = avformat_new_stream(o_fmt_ctx, NULL); + { + if (o_video_stream == NULL){ + printf("没有获取到视频流信息.\n"); + return -1; + } + + AVCodecParameters *oVcc; + oVcc = o_video_stream->codecpar; + + //平均比特率 + oVcc->bit_rate = 400000; + oVcc->codec_id = i_video_stream->codecpar->codec_id; + oVcc->codec_type = i_video_stream->codecpar->codec_type; + + oVcc->sample_aspect_ratio.num = i_video_stream->sample_aspect_ratio.num; + oVcc->sample_aspect_ratio.den = i_video_stream->sample_aspect_ratio.den; + + oVcc->extradata = i_video_stream->codecpar->extradata; + oVcc->extradata_size = i_video_stream->codecpar->extradata_size; + + oVcc->width = i_video_stream->codecpar->width; + oVcc->height = i_video_stream->codecpar->height; + oVcc->format = i_video_stream->codecpar->format; + + //帧率 + o_video_stream->r_frame_rate = i_video_stream->r_frame_rate; + } + + // oVcc->flags = i_video_stream->codec->flags; + // oVcc->flags |= CODEC_FLAG_GLOBAL_HEADER; + // oVcc->me_range = i_video_stream->codec->me_range; + // oVcc->max_qdiff = i_video_stream->codec->max_qdiff; + // // 最小量化器 + // oVcc->qmin = i_video_stream->codec->qmin; + // // 最大量化器 + // oVcc->qmax = i_video_stream->codec->qmax; + // + // // encoding parameters + // oVcc->qcompress = i_video_stream->codec->qcompress; + + + /**************************************** 音频 **********************************/ + o_audio_stream = avformat_new_stream(o_fmt_ctx, NULL); + { + if (o_audio_stream == NULL){ + printf("没有获取到音频流信息.\n"); + return -1; + } + + AVCodecParameters *oAcc; + oAcc = o_audio_stream->codecpar; + + oAcc->codec_id = AV_CODEC_ID_AAC; //AAC格式 + oAcc->codec_type = AVMEDIA_TYPE_AUDIO; + oAcc->bit_rate = 64000; + //采样率(音频) + oAcc->sample_rate = 44100; + //声道数(音频) + oAcc->channel_layout = AV_CH_LAYOUT_STEREO; + oAcc->channels = av_get_channel_layout_nb_channels(oAcc->channel_layout); + } + + /*******************************************************************************/ + + // 列出输出文件的相关流信息 + printf("------------------- 输出文件信息 ------------------\n"); + av_dump_format(o_fmt_ctx, 0, filename, 1); + printf("-------------------------------------------------\n"); + + avio_open(&o_fmt_ctx->pb, filename, AVIO_FLAG_WRITE); + + + if (!(o_fmt_ctx->flags & AVFMT_NOFILE)) { + + } + + if (!o_fmt_ctx->nb_streams) { + fprintf(stderr, "output file dose not contain any stream\n"); + return -1; + } + + // 根据文件名的后缀写相应格式的文件头 + if (avformat_write_header(o_fmt_ctx, NULL) < 0) { + fprintf(stderr, "Could not write header for output file\n"); + return -1; + } + + int last_pts = 0, last_dts = 0; + int au_pts = 0, au_dts = 0; + + int64_t pts = 0, dts = 0; + int64_t a_pts = 0, a_dts = 0; + + int64_t end_time = 0; + + while (!bStop) + { + end_time++; + AVPacket i_pkt; + av_init_packet(&i_pkt); + i_pkt.size = 0; + i_pkt.data = NULL; + + // 获取流中每一帧的数据流(包) + if (av_read_frame(i_fmt_ctx, &i_pkt) < 0 ) + break; + /* + * pts and dts should increase monotonically + * pts should be >= dts + */ + + if (i_pkt.stream_index == AVMEDIA_TYPE_VIDEO) + { + i_pkt.flags |= AV_PKT_FLAG_KEY; + pts = i_pkt.pts; + i_pkt.pts += last_pts; + dts = i_pkt.dts; + i_pkt.dts += last_dts; + i_pkt.stream_index = 0; + + printf("%lld %lld\n", i_pkt.pts, i_pkt.dts); + + static int num = 1; + printf("frame %d\n", num++); + + // 往输出流中写一个分包 + av_interleaved_write_frame(o_fmt_ctx, &i_pkt); + } + else if (i_pkt.stream_index == AVMEDIA_TYPE_AUDIO){ + i_pkt.flags |= AV_PKT_FLAG_KEY; + a_pts = i_pkt.pts; + i_pkt.pts += au_pts; + a_dts = i_pkt.dts; + i_pkt.dts += au_dts; + i_pkt.stream_index = 1; + + printf("%lld %lld\n", i_pkt.pts, i_pkt.dts); + + static int num = 1; + printf("frame %d\n", num++); + + // 往输出流中写一个分包 + av_interleaved_write_frame(o_fmt_ctx, &i_pkt); + } + + if (end_time == 2000) + { + bStop = YES; + } + } + + last_dts += dts; + last_pts += pts; + + au_dts += a_dts; + au_pts += a_pts; + + avformat_close_input(&i_fmt_ctx); + + // 写输出流(文件)的文件尾 + av_write_trailer(o_fmt_ctx); + + // 视频信息 + av_freep(&o_fmt_ctx->streams[0]->codecpar); + av_freep(&o_fmt_ctx->streams[0]); + + // 音频信息 + av_freep(&o_fmt_ctx->streams[1]->codecpar); + av_freep(&o_fmt_ctx->streams[1]); + + avio_close(o_fmt_ctx->pb); + av_free(o_fmt_ctx); + + return 0; +} + - (CGFloat)fpsAtOutput { return _glView.fps; diff --git a/ios/IJKMediaPlayer/IJKMediaPlayer/IJKMediaPlayback.h b/ios/IJKMediaPlayer/IJKMediaPlayer/IJKMediaPlayback.h index b5e50e07d6..2a161aa05c 100644 --- a/ios/IJKMediaPlayer/IJKMediaPlayer/IJKMediaPlayback.h +++ b/ios/IJKMediaPlayer/IJKMediaPlayer/IJKMediaPlayback.h @@ -103,6 +103,17 @@ typedef NS_ENUM(NSInteger, IJKMPMovieTimeOption) { - (UIImage *)thumbnailImageAtCurrentTime; +// 录制视频(只能录制RTSP流)格式mov +/** + * 录制视频(只能录制RTSP流)格式mov + * + * @param streamURL 流地址 + * @param filePath 录制的视频存储的路径 + * @param bStop 是否开始录制(NO开始, YES结束) + * @return int 返回-1,录制失败, 0为正常 + */ +- (int)rtsp2mov:(NSString *)streamURL storageFilePath:(NSString *)filePath isStop:(BOOL)bStop; + #pragma mark Notifications #ifdef __cplusplus From c32dc4b6694fa11ca36457b2f3dbf68a498be1ba Mon Sep 17 00:00:00 2001 From: NhatHM Date: Thu, 18 Jul 2019 15:25:28 +0700 Subject: [PATCH 2/2] Finished record --- ijkmedia/ijkplayer/ff_ffplay.c | 194 +++++++++++++ ijkmedia/ijkplayer/ff_ffplay.h | 5 + ijkmedia/ijkplayer/ff_ffplay_def.h | 11 + ijkmedia/ijkplayer/ijkplayer.c | 51 ++++ ijkmedia/ijkplayer/ijkplayer.h | 3 + .../IJKMediaPlayer.xcodeproj/project.pbxproj | 31 +- .../IJKFFMoviePlayerController.m | 268 +----------------- .../IJKMediaPlayer/IJKMediaPlayback.h | 14 +- 8 files changed, 305 insertions(+), 272 deletions(-) diff --git a/ijkmedia/ijkplayer/ff_ffplay.c b/ijkmedia/ijkplayer/ff_ffplay.c index 6ba2f3db19..925f9aa8a7 100755 --- a/ijkmedia/ijkplayer/ff_ffplay.c +++ b/ijkmedia/ijkplayer/ff_ffplay.c @@ -658,6 +658,19 @@ static int decoder_decode_frame(FFPlayer *ffp, Decoder *d, AVFrame *frame, AVSub } } while (d->queue->serial != d->pkt_serial); +// if (!ffp->is_first && pkt.pts == pkt.dts) { +// ffp->start_pts = pkt.pts; +// ffp->start_dts = pkt.dts; +// } + +// if (ffp->is_record) { +// if (0 != ffp_record_file(ffp, &pkt)) { +// ffp->record_error = 1; +// ffp_stop_recording_l(ffp); +// printf("avcodec_send_packet stop\n"); +// } +// } + if (pkt.data == flush_pkt.data) { avcodec_flush_buffers(d->avctx); d->finished = 0; @@ -3099,6 +3112,167 @@ static int is_realtime(AVFormatContext *s) return 0; } +int ffp_start_recording_l(FFPlayer *ffp, const char *file_name) +{ + assert(ffp); + VideoState *is = ffp->is; + + ffp->m_ofmt_ctx = NULL; + ffp->m_ofmt = NULL; + ffp->is_record = 0; + ffp->record_error = 0; + + if (!file_name || !strlen(file_name)) { + av_log(ffp, AV_LOG_ERROR, "filename is invalid"); + goto end; + } + + if (!is || !is->ic || is->paused || is->abort_request) { + av_log(ffp, AV_LOG_ERROR, "is,is->ic,is->paused is invalid"); + goto end; + } + + + av_log(ffp->is_record, AV_LOG_DEBUG, "NHATHM ------"); + if (ffp->is_record) { + av_log(ffp, AV_LOG_ERROR, "recording has started"); + av_log(ffp->is_record, AV_LOG_DEBUG, "NHATHM ------ 2"); + goto end; + } + + avformat_alloc_output_context2(&ffp->m_ofmt_ctx, NULL, NULL, file_name); + if (!ffp->m_ofmt_ctx) { + av_log(ffp, AV_LOG_ERROR, "Could not create output context filename is %s\n", file_name); + goto end; + } + ffp->m_ofmt = ffp->m_ofmt_ctx->oformat; + + for (int i = 0; i < is->ic->nb_streams; i++) { + AVStream *in_stream = is->ic->streams[i]; + AVStream *out_stream = avformat_new_stream(ffp->m_ofmt_ctx, in_stream->codec->codec); + if (!out_stream) { + av_log(ffp, AV_LOG_ERROR, "Failed allocating output stream\n"); + goto end; + } + + av_log(ffp, AV_LOG_DEBUG, "in_stream->codec;%p\n", in_stream->codec); + if (avcodec_copy_context(out_stream->codec, in_stream->codec) < 0) { + av_log(ffp, AV_LOG_ERROR, "Failed to copy context from input to output stream codec context\n"); + goto end; + } + + out_stream->codec->codec_tag = 0; + if (ffp->m_ofmt_ctx->oformat->flags & AVFMT_GLOBALHEADER) { + out_stream->codec->flags |= CODEC_FLAG_GLOBAL_HEADER; + } + } + + av_dump_format(ffp->m_ofmt_ctx, 0, file_name, 1); + + if (!(ffp->m_ofmt->flags & AVFMT_NOFILE)) { + if (avio_open(&ffp->m_ofmt_ctx->pb, file_name, AVIO_FLAG_WRITE) < 0) { + av_log(ffp, AV_LOG_ERROR, "Could not open output file '%s'", file_name); + goto end; + } + } + + if (avformat_write_header(ffp->m_ofmt_ctx, NULL) < 0) { + av_log(ffp, AV_LOG_ERROR, "Error occurred when opening output file\n"); + goto end; + } + + ffp->is_record = 1; + ffp->record_error = 0; + pthread_mutex_init(&ffp->record_mutex, NULL); + + return 0; +end: + ffp->record_error = 1; + return -1; +} + +int ffp_record_isfinished_l(FFPlayer *ffp) +{ + return 0; +} + +int ffp_record_file(FFPlayer *ffp, AVPacket *packet) +{ + assert(ffp); + VideoState *is = ffp->is; + int ret = 0; + AVStream *in_stream; + AVStream *out_stream; + + if (ffp->is_record) { + if (packet == NULL) { + ffp->record_error = 1; + av_log(ffp, AV_LOG_ERROR, "packet == NULL"); + return -1; + } + + AVPacket *pkt = (AVPacket *)av_malloc(sizeof(AVPacket)); + av_new_packet(pkt, 0); + if (0 == av_packet_ref(pkt, packet)) { + pthread_mutex_lock(&ffp->record_mutex); + if (!ffp->is_first) { + ffp->is_first = 1; + pkt->pts = 0; + pkt->dts = 0; + } else { + if (pkt->stream_index == AVMEDIA_TYPE_AUDIO) { + pkt->pts = llabs(pkt->pts - ffp->start_a_pts); + pkt->dts = llabs(pkt->dts - ffp->start_a_dts); + } + else if (pkt->stream_index == AVMEDIA_TYPE_VIDEO) { + pkt->pts = pkt->dts = llabs(pkt->dts - ffp->start_v_dts); + } + } + + in_stream = is->ic->streams[pkt->stream_index]; + out_stream = ffp->m_ofmt_ctx->streams[pkt->stream_index]; + + pkt->pts = av_rescale_q_rnd(pkt->pts, in_stream->time_base, out_stream->time_base, (AV_ROUND_NEAR_INF|AV_ROUND_PASS_MINMAX)); + pkt->dts = av_rescale_q_rnd(pkt->dts, in_stream->time_base, out_stream->time_base, (AV_ROUND_NEAR_INF|AV_ROUND_PASS_MINMAX)); + pkt->duration = av_rescale_q(pkt->duration, in_stream->time_base, out_stream->time_base); + pkt->pos = -1; + + if ((ret = av_interleaved_write_frame(ffp->m_ofmt_ctx, pkt)) < 0) { + av_log(ffp, AV_LOG_ERROR, "Error muxing packet\n"); + } + + av_packet_unref(pkt); + pthread_mutex_unlock(&ffp->record_mutex); + } else { + av_log(ffp, AV_LOG_ERROR, "av_packet_ref == NULL"); + } + } + return ret; +} + +int ffp_stop_recording_l(FFPlayer *ffp){ + assert(ffp); + if (ffp->is_record) { + ffp->is_record = 0; + pthread_mutex_lock(&ffp->record_mutex); + if (ffp->m_ofmt_ctx != NULL) { + av_write_trailer(ffp->m_ofmt_ctx); + if (ffp->m_ofmt_ctx && !(ffp->m_ofmt->flags & AVFMT_NOFILE)) { + avio_close(ffp->m_ofmt_ctx->pb); + } + avformat_free_context(ffp->m_ofmt_ctx); + ffp->m_ofmt_ctx = NULL; + ffp->is_first = 0; + } + pthread_mutex_unlock(&ffp->record_mutex); + pthread_mutex_destroy(&ffp->record_mutex); + av_log(ffp, AV_LOG_DEBUG, "stopRecord ok\n"); + } else { + av_log(ffp, AV_LOG_ERROR, "don't need stopRecord\n"); + } + return 0; +} + /* this thread gets the stream from the disk or the network */ static int read_thread(void *arg) { @@ -3392,6 +3566,26 @@ static int read_thread(void *arg) } for (;;) { + if (!ffp->is_first && pkt->pts == pkt->dts) { + if (pkt->stream_index == AVMEDIA_TYPE_AUDIO) { + ffp->start_a_pts = pkt->pts; + ffp->start_a_dts = pkt->dts; + } + } + if (pkt->stream_index == AVMEDIA_TYPE_VIDEO) { + if (!ffp->is_first) { + ffp->start_v_pts = pkt->pts; + ffp->start_v_dts = pkt->dts; + } + } + + if (ffp->is_record) { + if (0 != ffp_record_file(ffp, pkt)) { + ffp->record_error = 1; + ffp_stop_recording_l(ffp); + } + } + if (is->abort_request) break; #ifdef FFP_MERGE diff --git a/ijkmedia/ijkplayer/ff_ffplay.h b/ijkmedia/ijkplayer/ff_ffplay.h index ae21398272..9498356e8f 100644 --- a/ijkmedia/ijkplayer/ff_ffplay.h +++ b/ijkmedia/ijkplayer/ff_ffplay.h @@ -64,6 +64,11 @@ int ffp_is_paused_l(FFPlayer *ffp); int ffp_stop_l(FFPlayer *ffp); int ffp_wait_stop_l(FFPlayer *ffp); +/* record rtsp streaming */ +int ffp_start_recording_l(FFPlayer *ffp, const char *file_name); +int ffp_record_isfinished_l(FFPlayer *ffp); +int ffp_stop_recording_l(FFPlayer *ffp); + /* all in milliseconds */ int ffp_seek_to_l(FFPlayer *ffp, long msec); long ffp_get_current_position_l(FFPlayer *ffp); diff --git a/ijkmedia/ijkplayer/ff_ffplay_def.h b/ijkmedia/ijkplayer/ff_ffplay_def.h index e9dc920de9..f3ae86e25f 100755 --- a/ijkmedia/ijkplayer/ff_ffplay_def.h +++ b/ijkmedia/ijkplayer/ff_ffplay_def.h @@ -720,6 +720,17 @@ typedef struct FFPlayer { char *mediacodec_default_name; int ijkmeta_delay_init; int render_wait_start; + + AVFormatContext *m_ofmt_ctx; + AVOutputFormat *m_ofmt; + pthread_mutex_t record_mutex; + int is_record; + int record_error; + int is_first; + int64_t start_v_pts; + int64_t start_v_dts; + int64_t start_a_pts; + int64_t start_a_dts; } FFPlayer; #define fftime_to_milliseconds(ts) (av_rescale(ts, 1000, AV_TIME_BASE)) diff --git a/ijkmedia/ijkplayer/ijkplayer.c b/ijkmedia/ijkplayer/ijkplayer.c index 8ea4d4748e..25d338b8cf 100755 --- a/ijkmedia/ijkplayer/ijkplayer.c +++ b/ijkmedia/ijkplayer/ijkplayer.c @@ -796,3 +796,54 @@ int ijkmp_get_msg(IjkMediaPlayer *mp, AVMessage *msg, int block) return -1; } + +static int ijkmp_start_recording_l(IjkMediaPlayer *mp, const char *filePath) +{ + av_log(mp->ffplayer,AV_LOG_INFO,"cjz ijkmp_start_recording_l filePath %s",filePath); + return ffp_start_recording_l(mp->ffplayer, filePath); +} + +int ijkmp_start_recording(IjkMediaPlayer *mp,const char *filePath) +{ + assert(mp); + pthread_mutex_lock(&mp->mutex); + av_log(mp->ffplayer,AV_LOG_WARNING,"cjz ijkmp_start_recording --- "); + int retval = ijkmp_start_recording_l(mp,filePath); + printf("ijkmp_start_recording return == %d\n",retval); + pthread_mutex_unlock(&mp->mutex); + return retval; +} + +static int ijkmp_stop_recording_l(IjkMediaPlayer *mp) +{ + return ffp_stop_recording_l(mp->ffplayer); +} + +int ijkmp_stop_recording(IjkMediaPlayer *mp) +{ + assert(mp); + pthread_mutex_lock(&mp->mutex); + av_log(mp->ffplayer,AV_LOG_WARNING,"cjz ijkmp_stop_recording"); + int retval = ijkmp_stop_recording_l(mp); + pthread_mutex_unlock(&mp->mutex); + return retval; +} + +static int ijkmp_isRecordFinished_l(IjkMediaPlayer *mp) +{ + return ffp_record_isfinished_l(mp->ffplayer); +} + +int ijkmp_isRecordFinished(IjkMediaPlayer *mp) +{ + assert(mp); + pthread_mutex_lock(&mp->mutex); + av_log(mp->ffplayer,AV_LOG_WARNING,"cjz ijkmp_isRecordFinished "); + int retval = ijkmp_isRecordFinished_l(mp); + pthread_mutex_unlock(&mp->mutex); + return retval; +} + +int ijkmp_isRecording(IjkMediaPlayer *mp) { + return mp->ffplayer->is_record; +} diff --git a/ijkmedia/ijkplayer/ijkplayer.h b/ijkmedia/ijkplayer/ijkplayer.h index 9e99883dea..a3a352997d 100644 --- a/ijkmedia/ijkplayer/ijkplayer.h +++ b/ijkmedia/ijkplayer/ijkplayer.h @@ -150,6 +150,9 @@ struct SDL_Vout; #define IJKMP_OPT_CATEGORY_PLAYER FFP_OPT_CATEGORY_PLAYER #define IJKMP_OPT_CATEGORY_SWR FFP_OPT_CATEGORY_SWR +int ijkmp_start_recording(IjkMediaPlayer *mp, const char *filePath); +int ijkmp_stop_recording(IjkMediaPlayer *mp); +int ijkmp_isRecording(IjkMediaPlayer *mp); void ijkmp_global_init(); void ijkmp_global_uninit(); diff --git a/ios/IJKMediaPlayer/IJKMediaPlayer.xcodeproj/project.pbxproj b/ios/IJKMediaPlayer/IJKMediaPlayer.xcodeproj/project.pbxproj index 0f023aecb6..5eb9277f5d 100644 --- a/ios/IJKMediaPlayer/IJKMediaPlayer.xcodeproj/project.pbxproj +++ b/ios/IJKMediaPlayer/IJKMediaPlayer.xcodeproj/project.pbxproj @@ -1102,8 +1102,13 @@ LastUpgradeCheck = 0700; ORGANIZATIONNAME = bilibili; TargetAttributes = { + 5450AFC11E63EA4300568494 = { + DevelopmentTeam = YKSBCJ5G3S; + ProvisioningStyle = Automatic; + }; E654EA891B6B27E600B0F2D0 = { CreatedOnToolsVersion = 6.4; + DevelopmentTeam = YKSBCJ5G3S; }; E654EA931B6B27E600B0F2D0 = { CreatedOnToolsVersion = 6.4; @@ -1115,6 +1120,7 @@ developmentRegion = English; hasScannedForEncodings = 0; knownRegions = ( + English, en, ); mainGroup = E6903EB017EAF6C500CFD954; @@ -1393,10 +1399,13 @@ CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_UNREACHABLE_CODE = YES; + CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; + CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 0.8.8; DEBUG_INFORMATION_FORMAT = dwarf; DEFINES_MODULE = YES; + DEVELOPMENT_TEAM = YKSBCJ5G3S; DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 1; DYLIB_INSTALL_NAME_BASE = "@rpath"; @@ -1432,8 +1441,9 @@ "-read_only_relocs", suppress, ); - PRODUCT_BUNDLE_IDENTIFIER = "tv.danmaku.$(PRODUCT_NAME:rfc1034identifier)"; + PRODUCT_BUNDLE_IDENTIFIER = com.nhathm.IJKMediaFrameworkWithSSL; PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE_SPECIFIER = ""; SKIP_INSTALL = YES; TARGETED_DEVICE_FAMILY = "1,2"; VERSIONING_SYSTEM = "apple-generic"; @@ -1448,9 +1458,12 @@ CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_UNREACHABLE_CODE = YES; + CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; + CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 0.8.8; DEFINES_MODULE = YES; + DEVELOPMENT_TEAM = YKSBCJ5G3S; DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 1; DYLIB_INSTALL_NAME_BASE = "@rpath"; @@ -1483,8 +1496,9 @@ "-read_only_relocs", suppress, ); - PRODUCT_BUNDLE_IDENTIFIER = "tv.danmaku.$(PRODUCT_NAME:rfc1034identifier)"; + PRODUCT_BUNDLE_IDENTIFIER = com.nhathm.IJKMediaFrameworkWithSSL; PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE_SPECIFIER = ""; SKIP_INSTALL = YES; TARGETED_DEVICE_FAMILY = "1,2"; VERSIONING_SYSTEM = "apple-generic"; @@ -1499,10 +1513,12 @@ CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_UNREACHABLE_CODE = YES; + CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CURRENT_PROJECT_VERSION = 0.8.8; DEBUG_INFORMATION_FORMAT = dwarf; DEFINES_MODULE = YES; + DEVELOPMENT_TEAM = YKSBCJ5G3S; DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 1; DYLIB_INSTALL_NAME_BASE = "@rpath"; @@ -1533,10 +1549,11 @@ ); MACH_O_TYPE = staticlib; MTL_ENABLE_DEBUG_INFO = YES; - PRODUCT_BUNDLE_IDENTIFIER = "tv.danmaku.$(PRODUCT_NAME:rfc1034identifier)"; + PRODUCT_BUNDLE_IDENTIFIER = com.nhathm.IJKMediaFramework; PRODUCT_NAME = "$(TARGET_NAME)"; SKIP_INSTALL = YES; TARGETED_DEVICE_FAMILY = "1,2"; + VALID_ARCHS = "arm64 arm64e"; VERSIONING_SYSTEM = "apple-generic"; VERSION_INFO_PREFIX = ""; }; @@ -1552,6 +1569,7 @@ "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CURRENT_PROJECT_VERSION = 0.8.8; DEFINES_MODULE = YES; + DEVELOPMENT_TEAM = YKSBCJ5G3S; DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 1; DYLIB_INSTALL_NAME_BASE = "@rpath"; @@ -1579,10 +1597,11 @@ ); MACH_O_TYPE = staticlib; MTL_ENABLE_DEBUG_INFO = NO; - PRODUCT_BUNDLE_IDENTIFIER = "tv.danmaku.$(PRODUCT_NAME:rfc1034identifier)"; + PRODUCT_BUNDLE_IDENTIFIER = com.nhathm.IJKMediaFramework; PRODUCT_NAME = "$(TARGET_NAME)"; SKIP_INSTALL = YES; TARGETED_DEVICE_FAMILY = "1,2"; + VALID_ARCHS = "arm64 arm64e"; VERSIONING_SYSTEM = "apple-generic"; VERSION_INFO_PREFIX = ""; }; @@ -1683,7 +1702,7 @@ GCC_WARN_SHADOW = YES; GCC_WARN_UNINITIALIZED_AUTOS = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 7.0; + IPHONEOS_DEPLOYMENT_TARGET = 10.0; ONLY_ACTIVE_ARCH = YES; SDKROOT = iphoneos; STRIP_INSTALLED_PRODUCT = NO; @@ -1714,7 +1733,7 @@ GCC_WARN_SHADOW = YES; GCC_WARN_UNINITIALIZED_AUTOS = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 7.0; + IPHONEOS_DEPLOYMENT_TARGET = 10.0; SDKROOT = iphoneos; STRIP_INSTALLED_PRODUCT = NO; VALIDATE_PRODUCT = YES; diff --git a/ios/IJKMediaPlayer/IJKMediaPlayer/IJKFFMoviePlayerController.m b/ios/IJKMediaPlayer/IJKMediaPlayer/IJKFFMoviePlayerController.m index a622e8f91a..2e32840b00 100644 --- a/ios/IJKMediaPlayer/IJKMediaPlayer/IJKFFMoviePlayerController.m +++ b/ios/IJKMediaPlayer/IJKMediaPlayer/IJKFFMoviePlayerController.m @@ -450,6 +450,19 @@ - (BOOL)isPlaying return ijkmp_is_playing(_mediaPlayer); } +- (void)stopRecord { + ijkmp_stop_recording(_mediaPlayer); +} + +- (void)startRecordWithFileName:(NSString *)fileName { + const char *path = [fileName cStringUsingEncoding:NSUTF8StringEncoding]; + ijkmp_start_recording(_mediaPlayer, path); +} + +- (BOOL)isRecording { + return ijkmp_isRecording(_mediaPlayer); +} + - (void)setPauseInBackground:(BOOL)pause { _pauseInBackground = pause; @@ -761,261 +774,6 @@ - (UIImage *)thumbnailImageAtCurrentTime return nil; } -#pragma --mark 录制视频(只能录制RTSP流)格式mov -- (int)rtsp2mov:(NSString *)streamURL storageFilePath:(NSString *)filePath isStop:(BOOL)bStop -{ - // 输入 - AVStream *i_video_stream = NULL; //视频 - AVStream *i_audio_stream = NULL; //音频 - AVFormatContext *i_fmt_ctx = NULL; - - // 输出 - AVStream *o_video_stream = NULL; //视频 - AVStream *o_audio_stream = NULL; //音频 - AVFormatContext *o_fmt_ctx = NULL; - - // ijkmp_global_init(); - // // FFMPEG注册 - // ijkav_register_all(); - // avcodec_register_all(); - // av_register_all(); - // if(avformat_network_init() != 0){ - // printf("avformat_network_init() 失败 \n"); - // return -1; - // } - - // 视频存储的路径 - const char *filename = [filePath cStringUsingEncoding:NSUTF8StringEncoding]; - // 打开网络流或文件流 - if (avformat_open_input(&i_fmt_ctx, [streamURL cStringUsingEncoding:NSUTF8StringEncoding], NULL, NULL)!=0) - { - printf("无法打开输入流.\n"); - return -1; - } - - if (avformat_find_stream_info(i_fmt_ctx, NULL)<0) - { - printf("找不到流信息.\n"); - return -1; - } - - // 列出输入文件的相关流信息 - printf("---------------- 输入文件信息 ---------------\n"); - av_dump_format(i_fmt_ctx, 0, [streamURL cStringUsingEncoding:NSUTF8StringEncoding], 0); - printf("-------------------------------------------------\n"); - - - /* find first video stream */ - for (unsigned i=0; inb_streams; i++) - { - if (i_fmt_ctx->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) { - // 视频 - i_video_stream = i_fmt_ctx->streams[i]; - } - else if(i_fmt_ctx->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_AUDIO){ - // 音频 - i_audio_stream = i_fmt_ctx->streams[i]; - } - } - - if (i_video_stream == NULL) - { - fprintf(stderr, "没有找到任何视频流媒体\n"); - return -1; - } - - AVOutputFormat *oformat = av_guess_format(NULL, filename, NULL); - /** - * avformat_alloc_output_context2(AVFormatContext **ctx, AVOutputFormat *oformat, const char *format_name, const char *filename) - * - * ctx: 函数调用成功之后创建的AVFormatContext结构体 - * oformat: 指定AVFormatContext中的AVOutputFormat,用于确定输出格式。如果指定为NULL,可以设定后两个参数(format_name或者filename)由FFmpeg猜测输出格式 - * format_name: 指定输出格式的名称。根据格式名称,FFmpeg会推测输出格式。输出格式可以是“flv”,“mkv”等等 - * filename: 指定输出文件的名称 - */ - if(avformat_alloc_output_context2(&o_fmt_ctx, oformat, NULL, filename) !=0){ - fprintf(stderr, "初始化o_fmt_ctx结构体失败\n"); - return -1; - } - - /**************************************** 视频 **********************************/ - o_video_stream = avformat_new_stream(o_fmt_ctx, NULL); - { - if (o_video_stream == NULL){ - printf("没有获取到视频流信息.\n"); - return -1; - } - - AVCodecParameters *oVcc; - oVcc = o_video_stream->codecpar; - - //平均比特率 - oVcc->bit_rate = 400000; - oVcc->codec_id = i_video_stream->codecpar->codec_id; - oVcc->codec_type = i_video_stream->codecpar->codec_type; - - oVcc->sample_aspect_ratio.num = i_video_stream->sample_aspect_ratio.num; - oVcc->sample_aspect_ratio.den = i_video_stream->sample_aspect_ratio.den; - - oVcc->extradata = i_video_stream->codecpar->extradata; - oVcc->extradata_size = i_video_stream->codecpar->extradata_size; - - oVcc->width = i_video_stream->codecpar->width; - oVcc->height = i_video_stream->codecpar->height; - oVcc->format = i_video_stream->codecpar->format; - - //帧率 - o_video_stream->r_frame_rate = i_video_stream->r_frame_rate; - } - - // oVcc->flags = i_video_stream->codec->flags; - // oVcc->flags |= CODEC_FLAG_GLOBAL_HEADER; - // oVcc->me_range = i_video_stream->codec->me_range; - // oVcc->max_qdiff = i_video_stream->codec->max_qdiff; - // // 最小量化器 - // oVcc->qmin = i_video_stream->codec->qmin; - // // 最大量化器 - // oVcc->qmax = i_video_stream->codec->qmax; - // - // // encoding parameters - // oVcc->qcompress = i_video_stream->codec->qcompress; - - - /**************************************** 音频 **********************************/ - o_audio_stream = avformat_new_stream(o_fmt_ctx, NULL); - { - if (o_audio_stream == NULL){ - printf("没有获取到音频流信息.\n"); - return -1; - } - - AVCodecParameters *oAcc; - oAcc = o_audio_stream->codecpar; - - oAcc->codec_id = AV_CODEC_ID_AAC; //AAC格式 - oAcc->codec_type = AVMEDIA_TYPE_AUDIO; - oAcc->bit_rate = 64000; - //采样率(音频) - oAcc->sample_rate = 44100; - //声道数(音频) - oAcc->channel_layout = AV_CH_LAYOUT_STEREO; - oAcc->channels = av_get_channel_layout_nb_channels(oAcc->channel_layout); - } - - /*******************************************************************************/ - - // 列出输出文件的相关流信息 - printf("------------------- 输出文件信息 ------------------\n"); - av_dump_format(o_fmt_ctx, 0, filename, 1); - printf("-------------------------------------------------\n"); - - avio_open(&o_fmt_ctx->pb, filename, AVIO_FLAG_WRITE); - - - if (!(o_fmt_ctx->flags & AVFMT_NOFILE)) { - - } - - if (!o_fmt_ctx->nb_streams) { - fprintf(stderr, "output file dose not contain any stream\n"); - return -1; - } - - // 根据文件名的后缀写相应格式的文件头 - if (avformat_write_header(o_fmt_ctx, NULL) < 0) { - fprintf(stderr, "Could not write header for output file\n"); - return -1; - } - - int last_pts = 0, last_dts = 0; - int au_pts = 0, au_dts = 0; - - int64_t pts = 0, dts = 0; - int64_t a_pts = 0, a_dts = 0; - - int64_t end_time = 0; - - while (!bStop) - { - end_time++; - AVPacket i_pkt; - av_init_packet(&i_pkt); - i_pkt.size = 0; - i_pkt.data = NULL; - - // 获取流中每一帧的数据流(包) - if (av_read_frame(i_fmt_ctx, &i_pkt) < 0 ) - break; - /* - * pts and dts should increase monotonically - * pts should be >= dts - */ - - if (i_pkt.stream_index == AVMEDIA_TYPE_VIDEO) - { - i_pkt.flags |= AV_PKT_FLAG_KEY; - pts = i_pkt.pts; - i_pkt.pts += last_pts; - dts = i_pkt.dts; - i_pkt.dts += last_dts; - i_pkt.stream_index = 0; - - printf("%lld %lld\n", i_pkt.pts, i_pkt.dts); - - static int num = 1; - printf("frame %d\n", num++); - - // 往输出流中写一个分包 - av_interleaved_write_frame(o_fmt_ctx, &i_pkt); - } - else if (i_pkt.stream_index == AVMEDIA_TYPE_AUDIO){ - i_pkt.flags |= AV_PKT_FLAG_KEY; - a_pts = i_pkt.pts; - i_pkt.pts += au_pts; - a_dts = i_pkt.dts; - i_pkt.dts += au_dts; - i_pkt.stream_index = 1; - - printf("%lld %lld\n", i_pkt.pts, i_pkt.dts); - - static int num = 1; - printf("frame %d\n", num++); - - // 往输出流中写一个分包 - av_interleaved_write_frame(o_fmt_ctx, &i_pkt); - } - - if (end_time == 2000) - { - bStop = YES; - } - } - - last_dts += dts; - last_pts += pts; - - au_dts += a_dts; - au_pts += a_pts; - - avformat_close_input(&i_fmt_ctx); - - // 写输出流(文件)的文件尾 - av_write_trailer(o_fmt_ctx); - - // 视频信息 - av_freep(&o_fmt_ctx->streams[0]->codecpar); - av_freep(&o_fmt_ctx->streams[0]); - - // 音频信息 - av_freep(&o_fmt_ctx->streams[1]->codecpar); - av_freep(&o_fmt_ctx->streams[1]); - - avio_close(o_fmt_ctx->pb); - av_free(o_fmt_ctx); - - return 0; -} - - (CGFloat)fpsAtOutput { return _glView.fps; diff --git a/ios/IJKMediaPlayer/IJKMediaPlayer/IJKMediaPlayback.h b/ios/IJKMediaPlayer/IJKMediaPlayer/IJKMediaPlayback.h index 2a161aa05c..067addfb6e 100644 --- a/ios/IJKMediaPlayer/IJKMediaPlayer/IJKMediaPlayback.h +++ b/ios/IJKMediaPlayer/IJKMediaPlayer/IJKMediaPlayback.h @@ -102,17 +102,9 @@ typedef NS_ENUM(NSInteger, IJKMPMovieTimeOption) { @property (nonatomic) float playbackVolume; - (UIImage *)thumbnailImageAtCurrentTime; - -// 录制视频(只能录制RTSP流)格式mov -/** - * 录制视频(只能录制RTSP流)格式mov - * - * @param streamURL 流地址 - * @param filePath 录制的视频存储的路径 - * @param bStop 是否开始录制(NO开始, YES结束) - * @return int 返回-1,录制失败, 0为正常 - */ -- (int)rtsp2mov:(NSString *)streamURL storageFilePath:(NSString *)filePath isStop:(BOOL)bStop; +- (void)stopRecord; +- (void)startRecordWithFileName:(NSString *)fileName; +- (BOOL)isRecording; #pragma mark Notifications