Upload upstream chromium 108.0.5359.1
[platform/framework/web/chromium-efl.git] / media / filters / ffmpeg_glue.cc
1 // Copyright 2012 The Chromium Authors
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include "media/filters/ffmpeg_glue.h"
6
7 #include "base/check_op.h"
8 #include "base/metrics/histogram_functions.h"
9 #include "base/notreached.h"
10 #include "media/base/container_names.h"
11 #include "media/ffmpeg/ffmpeg_common.h"
12
13 namespace media {
14
15 // Internal buffer size used by AVIO for reading.
16 // TODO(dalecurtis): Experiment with this buffer size and measure impact on
17 // performance.  Currently we want to use 32kb to preserve existing behavior
18 // with the previous URLProtocol based approach.
19 enum { kBufferSize = 32 * 1024 };
20
21 static int AVIOReadOperation(void* opaque, uint8_t* buf, int buf_size) {
22   return reinterpret_cast<FFmpegURLProtocol*>(opaque)->Read(buf_size, buf);
23 }
24
25 static int64_t AVIOSeekOperation(void* opaque, int64_t offset, int whence) {
26   FFmpegURLProtocol* protocol = reinterpret_cast<FFmpegURLProtocol*>(opaque);
27   int64_t new_offset = AVERROR(EIO);
28   switch (whence) {
29     case SEEK_SET:
30       if (protocol->SetPosition(offset))
31         protocol->GetPosition(&new_offset);
32       break;
33
34     case SEEK_CUR:
35       int64_t pos;
36       if (!protocol->GetPosition(&pos))
37         break;
38       if (protocol->SetPosition(pos + offset))
39         protocol->GetPosition(&new_offset);
40       break;
41
42     case SEEK_END:
43       int64_t size;
44       if (!protocol->GetSize(&size))
45         break;
46       if (protocol->SetPosition(size + offset))
47         protocol->GetPosition(&new_offset);
48       break;
49
50     case AVSEEK_SIZE:
51       protocol->GetSize(&new_offset);
52       break;
53
54     default:
55       NOTREACHED();
56   }
57   return new_offset;
58 }
59
60 static void LogContainer(bool is_local_file,
61                          container_names::MediaContainerName container) {
62   base::UmaHistogramSparse("Media.DetectedContainer", container);
63   if (is_local_file)
64     base::UmaHistogramSparse("Media.DetectedContainer.Local", container);
65 }
66
67 FFmpegGlue::FFmpegGlue(FFmpegURLProtocol* protocol) {
68   // Initialize an AVIOContext using our custom read and seek operations.  Don't
69   // keep pointers to the buffer since FFmpeg may reallocate it on the fly.  It
70   // will be cleaned up
71   format_context_ = avformat_alloc_context();
72   avio_context_.reset(avio_alloc_context(
73       static_cast<unsigned char*>(av_malloc(kBufferSize)), kBufferSize, 0,
74       protocol, &AVIOReadOperation, nullptr, &AVIOSeekOperation));
75
76   // Ensure FFmpeg only tries to seek on resources we know to be seekable.
77   avio_context_->seekable =
78       protocol->IsStreaming() ? 0 : AVIO_SEEKABLE_NORMAL;
79
80   // Ensure writing is disabled.
81   avio_context_->write_flag = 0;
82
83   // Tell the format context about our custom IO context.  avformat_open_input()
84   // will set the AVFMT_FLAG_CUSTOM_IO flag for us, but do so here to ensure an
85   // early error state doesn't cause FFmpeg to free our resources in error.
86   format_context_->flags |= AVFMT_FLAG_CUSTOM_IO;
87
88   // Enable fast, but inaccurate seeks for MP3.
89   format_context_->flags |= AVFMT_FLAG_FAST_SEEK;
90
91   // Ensures format parsing errors will bail out. From an audit on 11/2017, all
92   // instances were real failures. Solves bugs like http://crbug.com/710791.
93   format_context_->error_recognition |= AV_EF_EXPLODE;
94
95   format_context_->pb = avio_context_.get();
96 }
97
98 bool FFmpegGlue::OpenContext(bool is_local_file) {
99   DCHECK(!open_called_) << "OpenContext() shouldn't be called twice.";
100
101   // If avformat_open_input() is called we have to take a slightly different
102   // destruction path to avoid double frees.
103   open_called_ = true;
104
105   // By passing nullptr for the filename (second parameter) we are telling
106   // FFmpeg to use the AVIO context we setup from the AVFormatContext structure.
107   const int ret =
108       avformat_open_input(&format_context_, nullptr, nullptr, nullptr);
109
110   // If FFmpeg can't identify the file, read the first 8k and attempt to guess
111   // at the container type ourselves. This way we can track emergent formats.
112   // Only try on AVERROR_INVALIDDATA to avoid running after I/O errors.
113   if (ret == AVERROR_INVALIDDATA) {
114     std::vector<uint8_t> buffer(8192);
115
116     const int64_t pos = AVIOSeekOperation(avio_context_->opaque, 0, SEEK_SET);
117     if (pos < 0)
118       return false;
119
120     const int num_read =
121         AVIOReadOperation(avio_context_->opaque, buffer.data(), buffer.size());
122     if (num_read < container_names::kMinimumContainerSize)
123       return false;
124
125     container_ = container_names::DetermineContainer(buffer.data(), num_read);
126     LogContainer(is_local_file, container_);
127
128     detected_hls_ =
129         container_ == container_names::MediaContainerName::CONTAINER_HLS;
130     return false;
131   } else if (ret < 0) {
132     return false;
133   }
134
135   // Rely on ffmpeg's parsing if we're able to succesfully open the file.
136   if (strcmp(format_context_->iformat->name, "mov,mp4,m4a,3gp,3g2,mj2") == 0)
137     container_ = container_names::CONTAINER_MOV;
138   else if (strcmp(format_context_->iformat->name, "flac") == 0)
139     container_ = container_names::CONTAINER_FLAC;
140   else if (strcmp(format_context_->iformat->name, "matroska,webm") == 0)
141     container_ = container_names::CONTAINER_WEBM;
142   else if (strcmp(format_context_->iformat->name, "ogg") == 0)
143     container_ = container_names::CONTAINER_OGG;
144   else if (strcmp(format_context_->iformat->name, "wav") == 0)
145     container_ = container_names::CONTAINER_WAV;
146   else if (strcmp(format_context_->iformat->name, "aac") == 0)
147     container_ = container_names::CONTAINER_AAC;
148   else if (strcmp(format_context_->iformat->name, "mp3") == 0)
149     container_ = container_names::CONTAINER_MP3;
150   else if (strcmp(format_context_->iformat->name, "amr") == 0)
151     container_ = container_names::CONTAINER_AMR;
152   else if (strcmp(format_context_->iformat->name, "avi") == 0)
153     container_ = container_names::CONTAINER_AVI;
154
155   DCHECK_NE(container_, container_names::CONTAINER_UNKNOWN);
156   LogContainer(is_local_file, container_);
157
158   return true;
159 }
160
161 FFmpegGlue::~FFmpegGlue() {
162   // In the event of avformat_open_input() failure, FFmpeg may sometimes free
163   // our AVFormatContext behind the scenes, but leave the buffer alive.  It will
164   // helpfully set |format_context_| to nullptr in this case.
165   if (!format_context_) {
166     av_free(avio_context_->buffer);
167     return;
168   }
169
170   // If avformat_open_input() hasn't been called, we should simply free the
171   // AVFormatContext and buffer instead of using avformat_close_input().
172   if (!open_called_) {
173     avformat_free_context(format_context_);
174     av_free(avio_context_->buffer);
175     return;
176   }
177
178   avformat_close_input(&format_context_);
179   av_free(avio_context_->buffer);
180 }
181
182 }  // namespace media